IntroductionWelcome| 00:04 | Hi, I am Simon Allardice, and this
is iOS 4 App Development New Features.
| | 00:09 | In the next few hours, we will take
your existing iPhone SDK dev skills and
| | 00:13 | quickly ramp them up to the work with
the new and improved features, frameworks, and
| | 00:17 | tools found in iOS 4.
| | 00:19 | We will see how to create applications
that use the new multitasking features,
| | 00:24 | delve into the new frameworks to
easily work with the built-in calendar and
| | 00:27 | with motion events, and explore new and better
ways of working with images, audio, and video.
| | 00:32 | We will even cover how to integrate
ads into your application with the
| | 00:36 | iAd framework and create apps that
work successfully across the growing
| | 00:41 | range of iPhone devices.
| | 00:43 | iOS 4 is a great update just
for regular uses of the iPhone,
| | 00:47 | but for app developers it opens up huge
possibilities for creating applications
| | 00:51 | that just weren't possible on
earlier versions of the iPhone OS.
| | 00:55 | Let's get started building these apps.
| | 00:57 | Welcome to iOS 4 App Development New Features!
| | Collapse this transcript |
| Prerequisites| 00:00 | When developing apps that runs on iOS 4, we
are still developing using the iPhone SDK.
| | 00:06 | That hasn't changed.
| | 00:07 | The core skills are iPhone SDK
development skills, and I expect you to have these.
| | 00:12 | You know Xcode.
| | 00:13 | You know Interface Builder.
| | 00:14 | You're good reading and writing Objective-C.
| | 00:16 | You are familiar with memory management
and delegation and how to interact with
| | 00:20 | the frameworks necessary
for iPhone SDK development.
| | 00:24 | If you need to first learn these skills
or even just brush up on your knowledge,
| | 00:28 | take a look at our iPhone
SDK Essential Training course.
| | 00:31 | Although it was written on an earlier
version of the iPhone SDK, it's still the
| | 00:35 | same core knowledge: the
language, the tools, and the process.
| | 00:39 | But if you're ready to go, then lets
start exploring what's new for developers
| | 00:42 | when working with this version
of the iPhone operating system.
| | Collapse this transcript |
|
|
1. Introduction to iOS SDK 4What's new in iOS 4 for developers?| 00:00 | So what's new in iOS 4 for developers?
| | 00:03 | Well, to begin with, the name is new.
| | 00:04 | We talk about iOS 4 instead of iPhone OS.
| | 00:08 | But if we are developers, that
doesn't mean we change to use the iOS SDK,
| | 00:12 | because at this point, Apple still
call our developer tools the iPhone
| | 00:16 | SDK, not the iOS SDK.
| | 00:18 | That might change in the future, but
right now we'd say that we use that iPhone
| | 00:22 | SDK 4 to develop apps for iOS 4, and
do make sure that you have the latest
| | 00:27 | version of the iPhone SDK
downloaded from developer.apple.com.
| | 00:31 | Now there is nothing
different about installing that SDK.
| | 00:34 | We still use Xcode and interface
builder. And you'll be forgiven for thinking
| | 00:38 | that if we are developing for iOS 4, using
the iPhone SDK 4, that must include Xcode 4.
| | 00:44 | But no, as of right now, July 2010, the
version of iPhone SDK 4 includes Xcode
| | 00:51 | version 3.2.3, and your version of
Xcode needs to be that, or higher.
| | 00:57 | Now as ever, we deal with a lot of
different areas when developing iPhone apps,
| | 01:01 | there are the tools that we
use: Xcode interface builder;
| | 01:04 | there's the language of Objective-C, and all
of the different frameworks for working
| | 01:08 | with specific areas; wrapping it all up
in the infrastructure, the process going
| | 01:13 | in and the background,
how your application runs.
| | 01:15 | So let's talk about those
pieces. First off, the tools.
| | 01:18 | They haven't really changed.
| | 01:20 | There are a couple of nice additions
in Xcode to make it easier to provision
| | 01:24 | your testing devices.
| | 01:25 | We will cover that later.
| | 01:26 | There's the language. The language hasn't
changed either, but there are a couple of
| | 01:31 | new features that almost
feel like a language change.
| | 01:34 | There is something called blocks.
| | 01:36 | We can now use a block to
create a chunk of reusable code;
| | 01:39 | it's almost like having a
function without having to name it.
| | 01:42 | Now many, if not most programming
languages support this, but they sometimes have
| | 01:46 | different names for it.
| | 01:47 | In C#, JavaScript, or PHP, you might
hear this block idea as being called an
| | 01:53 | anonymous function or a lambda.
| | 01:55 | Smalltalk calls this idea a block, as does Ruby.
| | 01:58 | And Objective-C takes a lot
of influence from Smalltalk,
| | 02:01 | so we are going to call these blocks.
| | 02:03 | Now if you have never come across
anonymous functions, lambdas, or closures is
| | 02:08 | another name you'll hear
here, don't worry about this.
| | 02:11 | We will see how to use
blocks later in the course.
| | 02:13 | They are very convenient in
situations like error handling, callbacks, and
| | 02:16 | delegation. They are going to
make your life a little easier.
| | 02:20 | And the idea of frameworks, well,
in iOS 4 we have new frameworks and
| | 02:24 | improved frameworks.
| | 02:25 | We'll go into several of the new
frameworks in this course, the frameworks for
| | 02:29 | working with calendar events,
| | 02:31 | a new framework for dealing with motion
data instead of having to deal with the
| | 02:34 | Accelerometer directly,
| | 02:36 | there is a new framework for
dealing with images in video,
| | 02:39 | there's even a specific framework for
delivering banner ads in your application
| | 02:43 | by connecting to Apple's iAd service.
| | 02:46 | Improved frameworks include better
movie players and AV foundation, and even
| | 02:50 | some changes to the
foundation in UIKit frameworks.
| | 02:53 | And then those infrastructure:
how does this all tie together?
| | 02:57 | And multitasking. Your applications no
longer completely exit when a user hits
| | 03:03 | the Home button on the iPhone 4.
| | 03:06 | And of all the changes in iOS 4, this is
the big one for developers, and this is
| | 03:11 | the big feature, even if
you don't care about it.
| | 03:14 | This is the big one because you have to
know how to deal with this new feature.
| | 03:18 | Some of the other stuff you can just ignore.
| | 03:20 | If you are writing an app that has no
video, you can just ignore all the new
| | 03:24 | cool video player classes,
and it would never be a problem.
| | 03:27 | You could ignore the calendar
functionality in iOS 4, or the CoreMotion framework
| | 03:31 | in iOS 4, and just not use them.
| | 03:34 | But you can't ignore multitasking.
| | 03:37 | Any app you build against
iOS 4 is a multitasking app.
| | 03:41 | It's not something you opt into;
| | 03:43 | it's the default behavior.
| | 03:44 | Now you might think, "Well, so what?"
| | 03:48 | What it means is the life cycle methods
we are used to don't work the way they
| | 03:52 | are used to, and that can really trip you up.
| | 03:54 | As you might imagine, we will go into
multitasking quite a bit in this course.
| | 03:58 | Now along the way from iPhone OS 3.1
to iOS 4.0, we picked up a lot of the
| | 04:04 | features built into iPhone OS 3.2.
| | 04:07 | This was the version of the iPhone OS
that never ran on the iPhone devices,
| | 04:11 | but only on the iPad.
| | 04:13 | It had things like gesture recognizers and
custom input views, and now we have those too.
| | 04:18 | For more in developing on 3.2, see
your developing iPad applications course.
| | 04:23 | As you can see, the developer changes
are significant, and we are going to work
| | 04:27 | with most of them, but here is this thing:
| | 04:30 | you already know that lot of the new
features in iOS 4 are targeted at the end user:
| | 04:34 | folders, Bluetooth keyboard support,
working with playlists, that kind of thing;
| | 04:39 | do make sure you're familiar with those, also.
| | 04:41 | It's really easy for developers to
become so focused on their own app and their
| | 04:45 | own favorite features,
| | 04:46 | they almost forget how a typical
user interacts with this device.
| | 04:50 | You need to make sure you're an
expert user of this device, not just an
| | 04:53 | expert developer on it.
| | Collapse this transcript |
| What's new in the development tools?| 00:00 | With all the new features in the iOS 4
operating system and the new iPhone 4
| | 00:05 | device itself, you might think that
there would be significant changes to
| | 00:09 | the developer tools.
| | 00:09 | But no, there aren't - or at least not yet.
| | 00:12 | On the initial release, version 4 of the
SDK, comes with version 3.2.3 of Xcode.
| | 00:19 | But Xcode 4 is coming, and Xcode 4 will be
a major, major change to the developer toolset.
| | 00:26 | But as of right now, July 2010, I'm
still on Xcode 3.2.3, which really does not
| | 00:31 | have a lot of new stuff in
it when compared to Xcode 3.2.
| | 00:35 | One nice addition, however, is
the changes to the Organizer window.
| | 00:40 | It's now a lot friendlier and if you
plug in a new device, you will be able to
| | 00:44 | choose to use that device for
development straight from Xcode, without having to
| | 00:48 | spend a lot of time in the
provisioning portal web site.
| | 00:51 | It's something celled Automatic Device
Provisioning, and in fact, you will see a
| | 00:57 | check box for it in the Provisioning
Profiles part of your organizer window.
| | 01:01 | In fact, if you hit Refresh, what
it's going to ask you to do is give your
| | 01:05 | credentials for the iPhone Dev Center,
the way you normally log on to the web
| | 01:09 | site and fetch any necessary
information from the web site.
| | 01:14 | Now we still do have to
spend some time in provisioning.
| | 01:17 | It's still a somewhat unpleasant part of
iPhone development, so any help there is welcome.
| | 01:23 | Other than that, there are some
additions to instruments that support multitasking
| | 01:27 | features, and there is a new instrument
called Automation, which helps you create
| | 01:32 | automated task on the
iPhone by using JavaScript.
| | 01:35 | But the general developer
toolset hasn't changed very much.
| | 01:38 | We are really all waiting on Xcode 4,
and you will hear much more about that at
| | 01:42 | lynda.com when that arrives.
| | 01:44 | But now we can go ahead and create iOS 4
applications using Xcode without having
| | 01:50 | to learn any major new section of
XCode or instruments or interface builder.
| | Collapse this transcript |
| Understanding the device improvements| 00:00 | Sure, most of the iPhone devices out
there in the world can run iOS 4, and they
| | 00:06 | don't all run it the same way.
| | 00:08 | But before we talk about the
differences between the devices, let's set the bar
| | 00:12 | first with the classic iOS 4 device,
the iPhone 4, released in June 2010.
| | 00:18 | Sure, there are a lot of great things
about this device, but as developers, we
| | 00:22 | have a different interest than
just the typical selling points.
| | 00:25 | Sure the width is great and the glass
is terrific, but we're interested in
| | 00:29 | things that can or will
affect our functionality.
| | 00:33 | First off is the Retina display, the
better display than 960 x 640 pixel display?
| | 00:40 | Previous iPhones are of course 480 x 310.
| | 00:44 | Now this can have an impact on the
graphics in your application, but as we'll
| | 00:47 | see, for typical apps you can kind of
forget about the resolution details and
| | 00:52 | have it just work, on either the
iPhone 4 or an earlier device.
| | 00:56 | You certainly don't need to worry
about the resolution difference like you do
| | 01:00 | when going from the iPhone to the iPad.
| | 01:03 | Now, there's the camera situation.
| | 01:05 | We have the new five megapixel still
camera with LED flash on the rear, which is
| | 01:10 | even capable of taking HD video in 720p,
| | 01:13 | and the new front facing camera used for
the FaceTime video calls or self portraits.
| | 01:19 | Now, a lot of people assume that these
are cameras that are just duplicates of
| | 01:23 | each other, but they are very different.
| | 01:25 | The front facing camera is optimized
for the FaceTime video calls and has
| | 01:29 | much lower resolution.
| | 01:31 | Photos taken with this camera
are only VGA, 640X480 pixels.
| | 01:37 | So you have a five-megapixel camera on
the rear and a 0.3-megapixel camera on
| | 01:43 | the front, certainly something to be
aware of if you're planning to integrate
| | 01:46 | photos and/or video taking in your application.
| | 01:49 | But what else? What can't we see?
| | 01:52 | Well, there's the processor.
| | 01:54 | The iPhone 4 has a faster A4
processor, same as in the iPad.
| | 01:58 | There's memory. Regardless of whether
you have an iPhone 4 with 16 gig or 32 gig
| | 02:03 | of storage, this has 512 megabytes of
RAM, twice as much as the iPhone 3GS and
| | 02:09 | even the iPad, four times as much as the iPhone 3G.
| | 02:13 | Now this is great for this device.
| | 02:16 | But the impact is deceptive if you
favor testing only here, as having more
| | 02:21 | memory and a faster processor is
forgiving to the developer in a way that's not
| | 02:25 | always useful, because your apps
may run very nicely on this device and
| | 02:30 | terribly on earlier devices, so more
than ever, test critically on older
| | 02:34 | devices you plan to ship on.
| | 02:36 | Now you can, of course, write an
app that only runs on the iPhone 4.
| | 02:41 | Apple, for example, released the iMovie
app that only works on this device, as
| | 02:45 | it's too demanding to run on earlier devices.
| | 02:48 | But Apple is intentionally pushing
the envelope there to showcase new
| | 02:52 | features on the phone. So what else?
| | 02:55 | Well, we have a gyroscope now
built into this device, along with the
| | 02:58 | accelerometers that we've always had,
and the compass that's been there
| | 03:02 | since the iPhone 3GS.
| | 03:04 | The end result is more
accurate and more usable motion data.
| | 03:07 | Now as developers, we even have a new
framework to make it easier to measure and
| | 03:12 | to react to motion within our apps.
| | 03:14 | So those are the main device
improvements to be aware of as a developer.
| | 03:18 | And it's going to be your choice of
whether to require them, to cater to them, or
| | 03:22 | even to ignore them.
| | 03:23 | Now certainly you need to be aware
of the differences in the way iOS 4 is
| | 03:28 | implemented on different devices.
| | 03:30 | We're going to cover that next.
| | Collapse this transcript |
| Exploring iOS 4 support across the different Apple devices| 00:00 | In the past three years, there have
been eight different iPhone devices, not
| | 00:04 | even counting the different memory sizes.
| | 00:06 | The current device line up, as of July
2010: we have the original iPhone, the
| | 00:11 | iPhone 3G and the iPhone 3GS, three
generations of iPod touch, the iPad and the iPhone 4.
| | 00:18 | When we take about iOS 4 Support, well,
let's take care of the easy ones first.
| | 00:23 | The original iPhone, released in
2007, is not supported at all by iOS 4.
| | 00:28 | On the other end, we have
of course, the iPhone 4.
| | 00:31 | That's given, it fully supports iOS 4.
| | 00:34 | The iPhone 3GS released in 2009 can
be upgraded to iOS 4 with full support,
| | 00:41 | including multitasking.
| | 00:43 | Obviously, there are some hardware
differences and the processor and memory are
| | 00:46 | less, but we get all the features.
| | 00:49 | Next, here's the challenging one.
| | 00:51 | The iPhone 3G from 2008, you can install
iOS 4 on this and get most of the iOS 4
| | 00:57 | features, but it doesn't support multitasking.
| | 01:00 | There isn't enough memory on the iPhone 3G to
support having multiple apps in the background.
| | 01:06 | So even after upgrading to iOS 4, when
you hit the Home button on an iOS app
| | 01:11 | running on a 3G, it will fully exit the way
it always has on iPhone OS 3.1 and earlier.
| | 01:17 | It also doesn't support
custom wallpaper on the Home screen.
| | 01:21 | Now the iPod touches - well, this
becomes tough because there's three
| | 01:24 | generations, but it's made easier
because they all correspond to a different
| | 01:28 | version of the iPhone.
| | 01:30 | The first iPod touch
cannot be upgraded to iOS 4.
| | 01:34 | The second generation, late 2008 iPod
touch, can be upgraded to iOS 4, but
| | 01:40 | because of hardware limitations, does
not support multitasking, custom wallpaper
| | 01:44 | or being connected to a Bluetooth keyboard.
| | 01:47 | It's very much like the iPhone 3G.
| | 01:49 | The third generation, 32 and 64 Gig
iPod touch from late 2009 and onwards does
| | 01:56 | support iOS 4 fully, including multitasking;
| | 01:59 | it's like the iPhone 3GS.
| | 02:01 | And here's the surprise for
a lot of people, the iPad.
| | 02:05 | It does not run iOS 4, at
least not yet, not in summer 2010.
| | 02:09 | iPad Support is still several months away.
| | 02:12 | The assumption is that it will be fully
supported, but right now it cannot be upgraded.
| | 02:17 | So here's the summary.
| | 02:19 | The original iPhone and the first gen iPod
touch, no support for iOS 4 and none expected.
| | 02:25 | The iPad, support is expected later in 2010.
| | 02:29 | The iPhone 3G and the second gen
iPod touch, this supports iOS 4, but with
| | 02:35 | multitasking and custom
wallpaper removed for performance reasons.
| | 02:39 | The iPhones 3GS and the iPod touch 3rd gen,
full support, but slower devices than the iPhone 4.
| | 02:45 | Now of course, even when devices can be
upgraded, there will remain many devices
| | 02:50 | that will be operating on the old OS,
with people either choosing not to
| | 02:54 | upgrade or just not connecting their
device to iTunes and never being prompted.
| | 02:59 | But as you can see, if you're building
apps that push the new features, you'll
| | 03:03 | need to consider the spread of support.
| | 03:05 | Bear in mind, you can specify that
only certain devices are supported when
| | 03:09 | publishing to the App Store and write
code inside your app to detect the
| | 03:13 | existence of the camera or video support.
| | 03:16 | That kind of code is
becoming more important than ever.
| | Collapse this transcript |
|
|
2. Multitasking in iOS 4Understanding multitasking concepts| 00:00 | So with multitasking, your applications
no longer completely exit when the user
| | 00:05 | hits the Home button on the iPhone.
| | 00:08 | I've said that this is the key feature
for developers in iOS, 4 simply because
| | 00:12 | you must deal with it in one way another.
| | 00:14 | You don't opt into multitasking;
| | 00:16 | it's the default behavior in iOS 4.
| | 00:19 | If you build your project to run against iOS 4,
| | 00:22 | it is assumed it supports multitasking.
| | 00:24 | But let's cover a couple of key concepts here.
| | 00:28 | First, what Apple means by multitasking
may not be what you mean by multitasking.
| | 00:33 | It certainly does not mean that
your app just runs all the time.
| | 00:38 | That would be a far too uncontrolled
state of affairs for Apple to allow.
| | 00:43 | There is a very short list of what your
app is allowed to do in the background.
| | 00:48 | Really, the first change for
developer is not how your application starts;
| | 00:52 | it's in how it exists,
or rather that is doesn't.
| | 00:56 | In iPhones 3.1, you open up an
application by tapping the icon.
| | 01:02 | Your user uses the application, they
press the Home button, and your app exists.
| | 01:08 | In that exit process, the OS will call,
applicationWillTerminate method in your
| | 01:12 | app delegate, and you'll have your
save state, or your cleanup code there.
| | 01:17 | But you have to be quick about it
because you have about five seconds to exit
| | 01:21 | before iPhone OS decides you're taking
too long and just dumps your process.
| | 01:25 | In iOS 4, however, your
application is launched the same way,
| | 01:30 | your user uses it, the user then
presses the Home button, and your application
| | 01:35 | does not completely exit when the
user goes back to the Home screen, but it
| | 01:40 | moves to a background state and
applicationWillTerminate is not called.
| | 01:45 | Your app has disappeared from the
iPhone screen, and it has entered that
| | 01:49 | background state, more of a
suspended state than an actively running one.
| | 01:53 | If the user taps your Apple icon to
run it again, your app moves in the
| | 01:57 | background state to the foreground state.
| | 01:59 | They can also double-click the Home
button to get a list of the applications
| | 02:04 | that are in the background.
| | 02:05 | Re-launch it that way, and this cycle continues.
| | 02:09 | And this is the key difference that we
don't just have Application Lifecycle
| | 02:13 | events for starting the app and
ending the app, but also for moving to the
| | 02:18 | background and moving to the
foreground, and those methods are
| | 02:21 | applicationDidEnterBackground and
applicationWillEnterForeground, and these
| | 02:26 | could be called many times between
the first applicationDidFinishLaunching
| | 02:31 | and your applicationWillTerminate.
| | 02:34 | However, your app can still be requiring
to exit all the way out, either because
| | 02:40 | iOS 4 itself decides it needs the
memory, or the user can also exit your
| | 02:45 | application by double-clicking the
Home button to see the list of apps, then
| | 02:49 | holding down and tapping the Minus button.
| | 02:52 | So the old
applicationWillTerminate is still an essential method.
| | 02:56 | And of course, if you're running
on iPhone 3G that doesn't support
| | 02:59 | multitasking in iOS 4,
| | 03:01 | it's just like running on iPhone OS 3.1;
| | 03:04 | your app will just call
applicationWillTerminate and exit.
| | 03:08 | But if you've transitioned to the
background, okay, what is your application
| | 03:14 | doing in the background?
| | 03:15 | Well, by default, nothing.
| | 03:18 | Oh sure, your app is taking up a little bit
of memory, but next to no processing cycles.
| | 03:23 | So if you had started a long running
calculation or a long running network
| | 03:27 | operation, those are not
just going on in the background.
| | 03:31 | That's not what happens.
| | 03:33 | If your app needs to perform an
operation while it's suspended in the
| | 03:37 | background, you must write
code to ask permission to do that.
| | 03:42 | And there are five things your app can
do in the background: three very specific
| | 03:47 | things and two generic ones.
| | 03:50 | Specifically, your app can: one, play audio -
| | 03:53 | you have a background
application playing audio, say over a network string;
| | 03:57 | two, react to location changes, for say,
creating a navigation app with prompts:
| | 04:03 | three, you can keep a voiceover IP
connection alive to make calls over your
| | 04:07 | Internet connection.
| | 04:08 | Now, these three things require you to
create a key in your info.plist file that
| | 04:14 | says, "Yes, I play background audio,"
or "Yes, I respond to location changes."
| | 04:19 | And without those entries,
nothing will happen in the background.
| | 04:23 | But here's the deal.
| | 04:23 | What if you look at that and think, well,
I don't want to do one of these things;
| | 04:28 | I want to do something else.
| | 04:29 | What if you want to run a long
running operation, a complex calculation
| | 04:33 | or rendering a file?
| | 04:35 | Well, unfortunately, you can't just continue
to run whatever you want in the background.
| | 04:40 | But when your app is shifting to a
background state, you can request time to
| | 04:45 | finish a long running operation.
| | 04:47 | Requesting time does not mean
you get free rein on the processor.
| | 04:52 | The time you're going to get depends on
what device you have, what else is going
| | 04:56 | on, because the OS can throttle
your process back or even terminate it.
| | 05:01 | Even when you're running a task in the
background, there's a property called
| | 05:05 | backgroundTimeRemaining that you can
query to see how impatient the OS is
| | 05:09 | becoming with you and how many
seconds you have left to run.
| | 05:12 | And finally, number five;
| | 05:14 | you can create what are
called local notifications.
| | 05:17 | These are effectively like having alerts from
your app while it's running in the background.
| | 05:22 | Those can be pushed to the user at a
specific time, so your app could be
| | 05:26 | suspended in the background and
still be sending messages to the user.
| | 05:30 | These are the things you can do.
| | 05:33 | But the list of what you can't or
shouldn't do is, of course, much larger.
| | 05:38 | However, some of the main ones are:
Don't so any graphical operations,
| | 05:43 | particularly not OpenGL calls.
| | 05:46 | If you try and execute code that draws
on the screen when your application is
| | 05:50 | suspended in the background, the iOS
4 will terminate your app instantly.
| | 05:55 | Don't do significant network operations.
| | 05:58 | Aside from allowing voiceover IP in
specific circumstance, the OS does not want
| | 06:02 | you to be making network calls, so
doing large downloads in the background is
| | 06:07 | unfortunately not feasible.
| | 06:09 | You also want to save your
state when moving to the background,
| | 06:12 | just as a best practice. Again,
the OS could be dumping your app.
| | 06:16 | If you have large objects taking up
large amounts of memory, release them.
| | 06:21 | All of these are very common
things we'll have to deal with;
| | 06:23 | there are a few more guidelines
that we'll explore as we go forward.
| | 06:27 | But the answer to, 'what should my app
do in the background' is always as much
| | 06:32 | as necessary, as little as possible,
and spend more time making sure your app
| | 06:38 | can go from a background to the
foreground and become active and responsive,
| | 06:42 | quickly and efficiently.
| | 06:44 | Let's take some of these
concepts and see how to implement them.
| | Collapse this transcript |
| Responding to activation events| 00:00 | The easiest way to understand the
code you need to support multitasking is
| | 00:04 | simply to see what Apple now
gives you in a standard Xcode project.
| | 00:09 | So if I create a new view-based
Application for the iPhone, I'll call this
| | 00:14 | one SimpleMultitasking,
| | 00:19 | expand this, and then in my Class's
folder, I'm simply going to select the
| | 00:23 | implementation file for the AppDelegate.
| | 00:25 | Get myself a bit more space here.
| | 00:28 | If I start coming down to see the
methods that have already been provided, we
| | 00:32 | have the standard
didFinishLaunchingWithOptions, where a lot of the work is done
| | 00:37 | to set our initial application up.
| | 00:39 | Then below, we have
applicationWillResignActive as a placeholder with nothing in it.
| | 00:45 | We have applicationDidEnterBackground,
applicationWillEnterForeground,
| | 00:49 | applicationDidBecomeActive,
and applicationWillTerminate.
| | 00:54 | Of course, applicationWillTerminate is
the very common method that we put in all
| | 01:00 | our safe state code in a
typical iPhone SDK 3.0 application.
| | 01:05 | But between ending
applicationWillTerminate and the beginning
| | 01:09 | applicationWillTerminate,
we have these other four.
| | 01:12 | Let me just shrink this down so we
can see them all at the same time.
| | 01:18 | Now the applicationWillResignActive and
applicationDidBecomeActive are not new.
| | 01:23 | You may or may not have used them, but
they've been there since you could first
| | 01:26 | code for the iPhone.
| | 01:27 | Your application becoming inactive is
what happens when you, say, get a phone
| | 01:32 | call or an SMS message
while your app is running.
| | 01:35 | So if you're in a game, it
gives you a chance to pause.
| | 01:38 | But the two that we're interested in
are applicationDidEnterBackground and
| | 01:43 | applicationWillEnterForeground.
| | 01:46 | But of course, we want to understand these
more in the general flow of the application.
| | 01:51 | What's the best way to do this?
| | 01:52 | Well, let's add some NSLog messages,
because it might not work exactly the way
| | 01:57 | you're expecting it to, if
you're looking at this right now.
| | 02:00 | So I'm going to quickly add a few NSLog
messages to these methods, just with the
| | 02:05 | name of the method in them.
| | 02:08 | So after adding a simple NSLog to
every one, I'm going to Build and Run this.
| | 02:14 | It's going to open up the Simulator.
| | 02:16 | I've got it at 50% right now.
| | 02:18 | It really doesn't matter, because
there is no application here.
| | 02:20 | I'm going to open up the console, so
we can see the message that appears.
| | 02:25 | So I can see that, immediately, I've had
the didFinishLaunchingWithOptions and the
| | 02:30 | applicationDidBecomeActive.
| | 02:32 | They've both been called.
| | 02:34 | Now one thing to notice: if I just go
back to my code here and just collapse
| | 02:38 | some of these methods, so
we can see them all together,
| | 02:41 | here is while I had
didFinishLaunchingWithOptions and
| | 02:46 | applicationDidBecomeActive, I did not
have the applicationWillEnterForeground.
| | 02:51 | That's not called the first
time the application loads.
| | 02:55 | So what can I do next?
| | 02:56 | Well, really, the deal is that I
need to exit this application.
| | 03:00 | So I'm going to click the Home
button on the iPhone Simulator.
| | 03:04 | We see over here on the console that
we had applicationWillResignActive and
| | 03:09 | applicationDidEnterBackground.
| | 03:12 | So what happens if I try and
launch the application again?
| | 03:14 | Well, I can go to the regular
screen on the iPhone, and click it.
| | 03:18 | We get
applicationWillEnterForeground, applicationDidBecomeActive.
| | 03:22 | Going back and doing that
multiple times, we're cycling between
| | 03:27 | applicationWillResignActive,
applicationDidEnterBackground, when it goes into the
| | 03:33 | suspended state, then
WillEnterForeground, and DidBecomeActive.
| | 03:37 | We can just continue going.
| | 03:39 | This is regardless of if I'm using the
icon on the actual screen itself or if
| | 03:44 | I double-click the Home button to
launch it from the list of the currently
| | 03:49 | running applications.
| | 03:50 | That's
applicationWillEnterForeground, applicationDidBecomeActive.
| | 03:51 | Now the last thing that I could do is,
after exiting this and going from the
| | 04:00 | background to a suspended state, I could
double-click the Home button, click and
| | 04:05 | hold on the application name, and click the
red Minus button to quit it all the way out.
| | 04:10 | Now notice that if I fully exit the
application, it does not seem to call
| | 04:16 | applicationWillTerminate.
| | 04:19 | So back in the code, if I look at
this, we've gone through all of the
| | 04:22 | others multiple times, and I'm
switching between the DidEnterBackground and
| | 04:26 | applicationWillEnterForeground, and the
associated DidBecomeActive and WillResignActive.
| | 04:32 | But what's an important thing to
understand when you're running this kind of
| | 04:35 | application is, what really is the difference
between a background state and a suspended state?
| | 04:41 | Now up to this point, I've talked about
your application being in a background,
| | 04:45 | or suspended state, as if
they are the same thing.
| | 04:47 | Well, in fact, as far as Apple is
concerned, there is a very specific difference
| | 04:51 | between an app being in the background and an
app being in a suspended state. The deal is this:
| | 04:58 | when your application is running, here
we have gray screen application and the
| | 05:02 | user presses the Home button, the
application moves to the background, and
| | 05:06 | applicationDidEnterBackground
is called, and it's here.
| | 05:10 | You could actually perform
some background operations.
| | 05:13 | You could play audio, do a voiceover IP,
request some time to perform a long operation.
| | 05:19 | But if you're not doing any of those
things, and we're not in this example, your
| | 05:24 | application moves from the
background instantly to the suspended state.
| | 05:28 | Okay, so what's the difference?
| | 05:30 | Well, when your application is in that
background suspended state, it can re-launch,
| | 05:35 | we can go back to the Home screen,
| | 05:37 | we can launch another application,
| | 05:39 | but if you fully terminate the
application, even specifically by going to the
| | 05:44 | red Minus button,
applicationWillTerminate will not be called because the
| | 05:49 | application is suspended.
| | 05:51 | It will simply be purged from memory.
| | 05:53 | However, if your application was
actually performing some behavior in the
| | 05:57 | background, such as playing audio,
doing voiceover IP, then it will be
| | 06:02 | considered as being in a
background state, not a suspended state, and
| | 06:06 | applicationWillTerminate
would actually be called.
| | 06:09 | Now for some people, this can be an
issue because it might seem almost random.
| | 06:13 | Well, when is
applicationWillTerminate called? It seems sometimes like it's
| | 06:17 | called and sometimes like it's not.
| | 06:18 | It's really, really very specific.
| | 06:20 | Is your application actually
doing anything in the background?
| | 06:24 | That's going to specify whether
applicationWillTerminate will be called.
| | 06:27 | Now one last thing to be aware of:
| | 06:30 | this applicationWillTerminate method
is still very important, because if
| | 06:34 | you're running on a device that doesn't
support multitasking - the iPhone 3G, for example-
| | 06:39 | when you exit, it won't go to
the applicationDidEnterBackground.
| | 06:42 | That's not supported.
| | 06:44 | It will immediately call
applicationWillTerminate.
| | 06:47 | That does mean it often is not out of
place to have the same save behavior being
| | 06:52 | performed from both
applicationWillTerminate and applicationDidEnterBackground.
| | 06:58 | So certainly make sure to test your
app behavior on all levels of device.
| | Collapse this transcript |
| Requesting time to finish operations| 00:00 | Probably the most complex thing you
can do with multitasking on the iPhone is
| | 00:04 | just to generically request time to
finish a long running operation when your
| | 00:09 | application is suspended.
| | 00:11 | And it's not that requesting time is so complex;
| | 00:14 | it's really all to do with the idea
that you don't know how much time you are
| | 00:17 | going to have, and the iPhone OS itself
doesn't know how much time it's going to give you.
| | 00:22 | But let's go through this.
| | 00:23 | I'm going to create a new project
called RequestCompletionTime, and save that.
| | 00:29 | Now the question is, when do I
start a long running operation?
| | 00:34 | Well, there are really two answers to this.
| | 00:36 | I could be running the application
and continually requesting operations to
| | 00:41 | happen that might themselves need
to continue while the application is
| | 00:46 | suspended, or I could do it when
the app moves into the background.
| | 00:50 | For our purposes here, I'm going to
choose the latter, which means that in my
| | 00:54 | AppDelegate, I'm going to put some code
into the applicationDidEnterBackground.
| | 00:58 | That means when the application is
moved to the background, now we are going to
| | 01:04 | request some time and start off a
long running background process.
| | 01:10 | Long running is bit of a hand-wavy term there.
| | 01:14 | How much time do you have?
Well, I don't really know.
| | 01:17 | I'll show you how you can find out,
but certainly it would be a good guess to
| | 01:21 | say imagine that you
have less than ten minutes.
| | 01:25 | So this is certainly not a place that
you are going to attempt to download two
| | 01:28 | gigabytes over a 3G connection, for example.
| | 01:31 | Now before I start creating a
background task, I'm going to just declare a
| | 01:36 | variable up here, just at the top, called
the UIBackgroundTaskIdentifier, and I'm
| | 01:44 | going to call it backgroundTask,
| | 01:45 | just so it's available all
over place if I should need it.
| | 01:51 | Then in applicationDidEnterBackground,
I'm going to tell the operating system I'm
| | 01:56 | about to begin a background task
that may need some time to finish.
| | 02:01 | The way that I do it is by calling the
beginBackgroundTaskWithExpirationHandler,
| | 02:05 | which is a method of the UI application.
| | 02:08 | Now I'm just going to complete this
first block here because it's likely to
| | 02:13 | look a little strange,
particularly if you haven't used blocks.
| | 02:17 | Let me open this up to show a bit.
| | 02:20 | What is happening is the argument that
is actually being passed into this method,
| | 02:27 | last from the opening carrot up at
this top right section here, down to the
| | 02:33 | closing square brace,
| | 02:34 | what we are really doing is
constructing a block of code that we are passing
| | 02:39 | into this method saying, "This is what
I want to have happened if the task is
| | 02:44 | about to run out of time."
| | 02:45 | This is what our expiration handler is.
| | 02:48 | This code will actually not be
executed when the code is reached by the
| | 02:52 | compiler, even the program.
| | 02:54 | It's really telling the program, hey,
for future reference if you ever need to
| | 02:58 | call the expiration
handler, this is what happens.
| | 03:01 | Now we are going to be talking explicitly
about blocks a little bit later on in the course.
| | 03:06 | Count this is a little
bit of exposure to see them.
| | 03:08 | Whenever you this carrot symbol, you are
looking at the new block feature in Objective-C.
| | 03:13 | Now the thing is here I'm going to
quite a bit of work for code that I hope
| | 03:19 | never, ever, ever gets executed,
because this is my expiration handler.
| | 03:24 | What it actually means is the OS has
said, "I realize you have asked for some
| | 03:27 | more time, but you are about to run out of time.
| | 03:30 | So I'm going to give you one chance to
tidy up anything you are doing and if you
| | 03:35 | don't, I'm going to terminate your application."
| | 03:38 | The thing is any task that we decide
that we want to execute as a background
| | 03:43 | task must have a matching
end background task call.
| | 03:48 | If it doesn't, the OS is very
likely to terminate the application.
| | 03:52 | But we are really not doing anything here.
| | 03:54 | This is just us saying "In the worst
case scenario, this is some cleanup code."
| | 03:59 | So continuing on from this, what I'm
going to add then next is to actually start
| | 04:03 | the long running task itself.
| | 04:05 | I'm doing this by calling dispatch_async.
| | 04:07 | And I'll talk about dispatch_
async in just a second here.
| | 04:12 | This is going to be my long running
code. Rather than have network calls or
| | 04:16 | calculations and essentially going to emulate
something that takes, in this case 15 seconds,
| | 04:21 | now what I'm going to do is to sleep for
five seconds and do a few NSLog messages.
| | 04:27 | One of the interesting things is that
during this long running background task I
| | 04:31 | can keep asking what's my
backgroundTimeRemaining,
| | 04:35 | if I am a little paranoid about
having plenty of time available, now we are
| | 04:39 | going to be NSLoging this during
this process while it's going on.
| | 04:45 | Then if I am done with all my
background process, I could actually call
| | 04:50 | application endBackgroundTask and
say, great I'm done. I'm finished.
| | 04:54 | I don't need to do any more.
| | 04:56 | Fortunately, if we want to go by
best practice, I wouldn't really just do
| | 05:00 | this here, that line.
| | 05:01 | So I'm going to delete that.
| | 05:03 | What I'm more likely to do is kind of
emulate this process up here, and that's
| | 05:08 | because I want to make sure that the
background task is getting marked off as
| | 05:12 | invalid, and there is no kind of
butting of heads that, say accidentally the
| | 05:17 | expiration handler had not kicked off
just as I was about to kick off my proper
| | 05:21 | ending of the background task.
| | 05:23 | The way that I am going to do this is by
doing another little code here, another
| | 05:28 | dispatch_async, and duplicating the
code that I had a little bit earlier.
| | 05:34 | Again, I have to make doubly
sure that I am always going to call
| | 05:37 | endBackgroundTask for anything that
I am trying to do in the background;
| | 05:40 | otherwise, my application
is going to get terminated.
| | 05:45 | Now what is this dispatch_async?
| | 05:47 | Well, this is a new feature of iOS 4.
| | 05:51 | This is part of what's
called Grand Central Dispatch.
| | 05:55 | This is something that, even though it
doesn't look like it right now, will
| | 05:58 | actually make the process of doing
multithreaded code very, very easy indeed.
| | 06:03 | What I am actually having to write
here is some of the more complex code you
| | 06:07 | will ever have to do
involving Grand Central Dispatch.
| | 06:10 | Now as you are looking at this and
thinking, good lord, this is an awful lot of
| | 06:14 | stuff to do some very basic, basic behavior.
| | 06:18 | I agree, completely, with you.
| | 06:21 | Do bear in mind that if you need to do
this a lot, most of what you will be able
| | 06:25 | to do is kind of boilerplate a lot of
this code, just changing the important
| | 06:30 | stuff for your own needs, making sure
that the work you are doing is actually
| | 06:34 | happening correctly.
| | 06:35 | And just making sure that should you
ever need to use your expiration handler,
| | 06:39 | because you didn't get enough time to
finish, that you are tiding up whatever
| | 06:43 | things needs to be done.
| | 06:44 | But at the moment, if you are looking
at this and some of these carrot symbols
| | 06:48 | are looking a bit weird, and this all
dispatch_async is looking a little odd,
| | 06:52 | just count this as kind of forward exposure.
| | 06:54 | We are going to get both into blocks
and Grand Central Dispatch a little
| | 06:58 | later in the course.
| | 07:00 | So what I'm going to do now is at the
end of this I'm actually going to just
| | 07:04 | do an NSLog message.
| | 07:05 | Reached the end of
ApplicationDidEnterBackground. I'm done.
| | 07:10 | Why am I doing this one?
| | 07:11 | Well, because what I want to prove to
you is what's actually going to happen
| | 07:15 | when this code is executed.
| | 07:17 | We should be able to hit the Home
button after the application is running, and
| | 07:21 | we are going to jump into
applicationDidEnterBackground.
| | 07:24 | We are going to run immediately through all
this code to set up the expiration handler.
| | 07:29 | We are going to run through this code
to start kicking off our work on another
| | 07:35 | thread in the background, and I should
immediately get this message going out,
| | 07:39 | and our application move into the background.
| | 07:41 | Then what should be happening is I
should get this background work going off and
| | 07:48 | having my NSLog messages kick out
every five seconds or so for 15 seconds.
| | 07:53 | So let's see if that is
indeed what is going to happen.
| | 07:57 | I'm going to switch to my simulator
here, save and build this application, and
| | 08:02 | click build and run.
| | 08:05 | Now the application itself doesn't do anything.
| | 08:07 | While it's running, I do not
have any user interface elements.
| | 08:10 | I am going to open up my console here, just
to have it open when I exit the application.
| | 08:18 | There we go! I can see I got to
the end of ApplicationDidEnterBackground.
| | 08:22 | Within a couple of seconds, I
want to see a message here.
| | 08:25 | Time remaining is 594 seconds.
| | 08:27 | Then it's down to 589 seconds,
and it should be about 584 seconds,
| | 08:33 | at which point we are done with our
long running operation, and silently our
| | 08:38 | application will now be moving from the
background state to the suspended state,
| | 08:43 | and this is exactly what I
would expect to see here.
| | 08:46 | So I can see that in the simulator what
happening here is I am getting about 600
| | 08:51 | seconds, which are about 10
minutes or very close to that.
| | 08:55 | That is what happens in the simulator,
and if you run it in the device, you will
| | 08:59 | often find about 10 minutes on average, too.
| | 09:01 | That can be affected by what other
applications are running on your machine,
| | 09:06 | how much memory you have got, how many
applications have been opened at the same time.
| | 09:10 | So it's not something you
can count on being 10 minutes.
| | 09:14 | That may change down, or even
up when new devices come along.
| | 09:18 | But this is the process that you need to
use if you want to request generic time
| | 09:24 | in multitasking to finish off some long
running task for your own applications.
| | 09:29 | Again, if you are new to the blocks
and new to this dispatch_async, we'll be
| | 09:34 | covering that a little later.
| | 09:35 | It's a good example of some of
the more interesting things that you can
| | 09:40 | do with iOS 4, will actually require
you to have some knowledge of both blocks
| | 09:44 | and GDC.
| | Collapse this transcript |
| Using local notifications| 00:00 | What I am about to talk about is not
officially part of multitasking, though it
| | 00:04 | is new in iOS 4.0, something called
LocalNotifications, and they can feel a little
| | 00:10 | bit like multitasking, because it is
the idea of sending messages to the user
| | 00:15 | even when your application
isn't officially running.
| | 00:18 | It's quite similar to the available
Push Notifications that you might get from
| | 00:23 | say applications like Facebook.
| | 00:25 | They are going to pop up messages
even when application is not running.
| | 00:28 | Let's go ahead and see how they are done.
| | 00:30 | I have a very simple view-based
application, where the one button on, my classic
| | 00:35 | one-button App, and right now it's
hooked up to a scheduleNotification method
| | 00:40 | here called IBAction, and it
doesn't actually have anything in it.
| | 00:44 | These are actually very simple to create,
but they have a couple of impacts that
| | 00:49 | are worth knowing about.
| | 00:50 | So I am going to actually
allocate a new local notification:
| | 00:54 | it's UILocalNotification.
| | 00:55 | You don't need any special frameworks
or import statements to make this work.
| | 01:00 | And then I am going to set some
information about it, which is basically when do
| | 01:04 | we want to this to fire?
| | 01:05 | We can set it for pretty much anytime
in the future, though there is not much
| | 01:08 | point for setting it for years in the
future, as the iPhone OS will only keep
| | 01:13 | track of about 64 of these
before it starts dumping them.
| | 01:17 | So I am going to do something very
simple that says, this notification is going
| | 01:21 | to fire off - meaning it's the fire date -
will be 15 seconds from whenever this
| | 01:27 | code is called, just so we can see the
impact of it, but you could schedule this
| | 01:31 | for the equivalent of an alarm
call for some date in the future.
| | 01:35 | You can also put recurrences
on it to that kind of thing.
| | 01:38 | I am then going to set some details
about it, that it's going to pop up a
| | 01:42 | message saying, "Missing you already!"
that it will open up a button that allows
| | 01:47 | you to open or view something, which
really is taking us to open the application,
| | 01:52 | so I am going to live that at View,
and then what I am going to do is set the
| | 01:56 | badge on the application icon.
| | 01:58 | What this mean is the little
circular red icon that will say, one, or two
| | 02:02 | or three, if you have a number of
events that you meant to review, always
| | 02:07 | like looking at mail, when your mail says if
you got four or five or six mails to read.
| | 02:11 | I am just going to say one, because I
am not imagining that this application
| | 02:16 | will have any other options than one,
and then I am going to gather together any
| | 02:21 | custom data that I needed to save with
the notification, and I just do this by
| | 02:25 | creating an NSDictionary, full of
whatever values I want to have in it. In this
| | 02:30 | case, I will have some value of
ABCD1234 for a key called yourKey, and this is
| | 02:36 | just the idea of packaging together
some custom information that you might want
| | 02:40 | to save for this notification, if
that's important to you. This is optional.
| | 02:44 | You don't have to do it.
| | 02:45 | And then I am going to go ahead
and schedule the notification.
| | 02:48 | This is all we need to do.
| | 02:49 | It's a call to UIApplication that says
scheduleNotification and pass in that
| | 02:55 | object. Then I can
actually go ahead and release it.
| | 02:59 | So I am going to save this,
and I am going to build this.
| | 03:02 | Build has succeeded, and
let's take a little look at this.
| | 03:05 | I am going to click Build and Run, and
this does work in the simulator, so I am
| | 03:08 | just going to open this up, 100% here.
| | 03:11 | I will click the button
to schedule a notification.
| | 03:13 | It shouldn't matter now if my
application is running or if it's not running.
| | 03:17 | It could be fully active, it could
be in the background, or it could
| | 03:21 | be completely closed.
| | 03:23 | What will happen is when that
notification hits, will pop up the
| | 03:28 | LocalNotification message, the top of
the message, the headline is the name of
| | 03:32 | the application and we get our choice
to View, which means open the application,
| | 03:37 | or just Close the message and dismiss it.
| | 03:40 | You will also notice that the badge
has been applied to our icon in the
| | 03:44 | background here on springboard.
| | 03:46 | So I could click Close.
| | 03:47 | I could click View.
| | 03:48 | One of the issues here, if I click View,
we are going back to the application.
| | 03:52 | It's just restoring from the
background, and I go back here.
| | 03:56 | We have still got that badge because
there is nothing automatically that's
| | 03:59 | actually going to take it off.
| | 04:00 | So I do need to do a little bit more
work to actually deal with this, because
| | 04:04 | there is really a couple of
things I want to take care of.
| | 04:07 | One is making that badge go away
when we reenter the application.
| | 04:12 | But the issue with that is, well
sometimes we might be opening the
| | 04:16 | application from scratch, where
it's already completely terminated and
| | 04:20 | completely closed - maybe they
restarted the phone - and sometimes it's just
| | 04:25 | restoring from the background.
| | 04:27 | So there is a couple of
different ways of dealing with that.
| | 04:29 | Let's go ahead and see that.
| | 04:31 | I am going to jump over into my
applications AppDelegate, and what I am going to
| | 04:38 | find here is my application
DidFinishLaunchingWithOptions. This is the classic
| | 04:43 | DidFinishLaunchingWithOptions method.
This will be called when the application
| | 04:48 | first launches from being totally closed,
| | 04:51 | not running in the background -
completely closed on the device.
| | 04:54 | Now what I am going to do, just before
we have the return YES statement, is I am
| | 04:59 | going to add a little line of code here
that actually says reset the badge, so
| | 05:03 | if we open up from scratch, it sets it
back to 0, and if it was 0 already, no big
| | 05:08 | deal. Then what we are going to do is
ask a little question, try to find out,
| | 05:12 | did we launch because of that notification?
| | 05:16 | Now we can tell this by examining our
launchOptions. Of course, this is the
| | 05:21 | applicationDidLaunchWithOptions, and
I am looking here to see, did I have a
| | 05:25 | LocalNotificationKey.
| | 05:28 | I will try and get that object, and if
that object is not nil, then I can say
| | 05:33 | okay, I am going to create an Alert
view that comes out with the message that
| | 05:37 | says, you came back, the
app was obviously closed.
| | 05:40 | message is "Nice to see you again!"
| | 05:42 | and just cancelButtonTitle,
nothing special there.
| | 05:45 | Show the alert and then release it.
| | 05:47 | But this code can prove to us that we
will accept any custom information about
| | 05:51 | that local notification if our app was
completely closed. And what this should
| | 05:56 | alert you to is that the local
notification part is not really multitasking.
| | 06:01 | It's not our app that's doing it.
| | 06:03 | If our application is completely
closed, there is nothing really in the
| | 06:07 | background to be able to pop up
that Alert view, and indeed it isn't
| | 06:11 | really multitasking.
| | 06:12 | What happens is when you schedule this
local notification, you are telling the
| | 06:17 | iOS operating system to handle, and
it's keeping its own little database of
| | 06:21 | those notifications.
| | 06:23 | However, the issue is this code will
not be called where in the application did
| | 06:28 | finish launching with options, and
this wouldn't be called if we were just
| | 06:31 | restoring from the background,
if we just had been suspended.
| | 06:36 | So what I also need to do is add another
block of code here, a different method.
| | 06:42 | In this case, it's going to be the application:
| | 06:44 | didReceiveLocalNotification,
passing in a LocalNotification.
| | 06:49 | I am going to reset the
badge number back to 0 again.
| | 06:53 | I am going to do something very similar
if the object exists. In this case, I am
| | 06:59 | actually going to see, was there
any customInfo from the dictionary.
| | 07:02 | If you remember in my View Controller,
I created just an NSDictionary and gave
| | 07:06 | it some dummy data in there, and then
this time I am going to actually pull that
| | 07:10 | data out to prove that we can get to it.
| | 07:13 | I am going to then construct an Alert
view that actually gives another message
| | 07:16 | that says the app was running
when this alert view popped up.
| | 07:20 | I'll pop out that message of the
custom information that I retrieved and then
| | 07:24 | just a Cancel button title
of OK, nothing special here.
| | 07:29 | Show the alert and release it and close that.
| | 07:32 | Save and build this application,
build has succeeded, and I am going to go
| | 07:35 | ahead and Build and Run.
| | 07:40 | So it opens up in the Simulator.
| | 07:41 | I am going to press button
to schedule a notification.
| | 07:44 | I am going to exit the application.
| | 07:46 | Not only that, I am going to double-
click to go to the multitasking interface
| | 07:51 | and select the Minus button
to make it completely go away.
| | 07:54 | So our application is
totally closed at this point.
| | 07:57 | Right now, I wouldn't expect to
see a badge number until now.
| | 08:01 | Our LocalNotification pops up, our badge
number is increased and is now number 1 in
| | 08:06 | the back, and if I click this View
button, I should see the message saying that
| | 08:10 | the application was closed.
| | 08:12 | Welcome back, but the application was closed.
| | 08:15 | So we open up, You came back!
| | 08:17 | (App was closed) nice to see you again.
| | 08:19 | Okay, let's schedule another
notification, and this time, I am just going to go
| | 08:24 | back to springboard and give it a few seconds.
| | 08:26 | It wouldn't really matter if my
application was fully active or if it was just
| | 08:31 | in the background here; the
same behavior would happen.
| | 08:34 | In this case, we get the badge increased,
we get the LocalNotification, but when
| | 08:38 | I click View, this time around
it should say the application
| | 08:41 | was running and should pull any custom
data out of that dictionary object, in
| | 08:46 | this case ABCD1234, and that looks correct.
| | 08:50 | So I am going to click OK and
exit out of the application.
| | 08:54 | Now of course, if you want to take it
a little further, you will find that
| | 08:58 | there are different abilities of the
UILocalNotification to obviously schedule
| | 09:03 | multiple LocalNotifications, and
because of that to also cancel all
| | 09:07 | LocalNotifications that this
application may have created, but this is the core
| | 09:12 | of how we create, manage, and reacts
to UILocalNotifications on the iPhone in
| | 09:19 | iOS 4.
| | Collapse this transcript |
| Opting out: Making an app that doesn't need multitasking| 00:00 | So what if you want to avoid
this whole multitasking idea?
| | 00:04 | Well, first, do remember that for most
applications you won't really be doing
| | 00:08 | anything in the background anyway, and
your app will move to the background and
| | 00:12 | then move to this
suspended string and do nothing.
| | 00:15 | It really doesn't take a lot of effort
to add the applicationDidEnterBackground
| | 00:20 | and applicationWillEnterForeground
methods, and it is recommended by Apple that
| | 00:24 | you support multitasking.
| | 00:26 | But yes, if your application really
doesn't lend itself to being suspended, and
| | 00:31 | you want to make sure that it
terminates and exists all the way out when the
| | 00:35 | user presses the Home button,
that's possible, and it's very simple.
| | 00:39 | So I am going to show you an
existing very simple application here.
| | 00:42 | We've just got straightforward
NSLog messages for these different
| | 00:46 | application delegates methods.
| | 00:48 | So we can see that
whether they are working or not.
| | 00:52 | So if we run, this just using
the simulator, right now it is
| | 00:56 | supporting multitasking.
| | 00:58 | If I open up my console, I
can see that I've got the
| | 01:02 | didFinishLanchingWithOptions,
applicationDidBecomeActive.
| | 01:06 | If I come back out, we're
resigning as the active application.
| | 01:09 | We're entering the background, and as
I open it up again, we're entering the
| | 01:13 | foreground, and so on.
| | 01:15 | Same kind of stuff that would just
continually go on, and on, and on.
| | 01:21 | So I will quit out of
that and go back into Xcode.
| | 01:24 | All I am going to do to effectively
turn off multitasking is go into my
| | 01:29 | info.plist file, come down to the bottom of it,
| | 01:33 | it really doesn't matter, but just
press Return, and what I am looking for, up at
| | 01:38 | the top here, is Application
does not run in background.
| | 01:42 | When that's selected, it becomes a Boolean.
| | 01:45 | It's just a little check
box here that I can check.
| | 01:47 | Yes, the application does
not run in the background.
| | 01:51 | Technically speaking, this key, if I
wanted to look at it as plain text - I'll
| | 01:57 | save it first - is called
UIApplicationExitsOnSuspend.
| | 02:03 | But you can edit this file, whether you
like to edit raw XML or whether you like
| | 02:07 | to use it as a property list.
| | 02:09 | It doesn't really matter.
| | 02:11 | So I am going to save that
and click Build and Run again.
| | 02:14 | I'll open up my Console.
| | 02:17 | Now you can see here that I have the
didFinishLanchingWithOptions has been
| | 02:21 | called, as has applicationDidBecomeActive.
| | 02:25 | But now if I press the Home button,
what we are going to see is I'll get
| | 02:29 | the applicationDidEnterBackground,
but I immediately also jump to the
| | 02:33 | applicationWillTerminate, which
was not being called, if we were
| | 02:37 | supporting multitasking.
| | 02:39 | Now the way the user interface works on
a multitasking device is even if you say
| | 02:44 | you don't support it, if I were to
double-click the Home button, I will see
| | 02:48 | that still there, and it will open back up.
| | 02:51 | But it's just to make the UI consistent,
whether you're supporting multitasking
| | 02:54 | or not. That application did exit all the
way out, and the memory was purged from it.
| | 03:01 | And one interesting thing to take from
this is even when you've checked that
| | 03:05 | value, that you're saying we don't run
in the background, you will see that the
| | 03:09 | applicationDidEnterBackground method is called.
| | 03:13 | Interestingly, the
applicationWillEnterForeground is never called if you have
| | 03:18 | that check box checked that the
application does not run in background.
| | 03:22 | So if you do have an application where
you really need to turn that off, that is
| | 03:25 | imply the way to do it, but again,
bear in mind that Apple does make it a strong
| | 03:30 | recommendation that all apps in the
App Store should support multitasking
| | 03:35 | really just to that level of the fast
applications switching - allowing it to go
| | 03:40 | from the foreground to the
background and back to the foreground again.
| | Collapse this transcript |
| Playing audio in the background| 00:00 | As one of the things that we can
now do with multitasking is allow our
| | 00:04 | applications to play audio in the background.
| | 00:07 | I am now going to show you how we can
take an existing application that plays
| | 00:10 | just some simple environmental sounds,
and I am going to convert that so it will
| | 00:15 | play those songs in the background.
| | 00:16 | So right now a very straightforward app.
| | 00:18 | It's got one Play button on, loads
just some environmental sounds, and
| | 00:25 | starts playing them.
| | 00:26 | In my viewDidLoad is where
I have the code to do this.
| | 00:29 | It's loading in rainstrom.mp3, creating
an instance of AVaudio player, and just
| | 00:35 | getting that set up and ready to play.
| | 00:37 | I've got the normal Delegate method
here: audioPlayerDidFinishPlaying
| | 00:41 | to reset the contents of the button,
and just a playPause Toggle method here,
| | 00:46 | that we will switch it on or off.
| | 00:48 | So to covert this into one that
supports multitasking and supports it well,
| | 00:53 | there is actually two things that I want to do.
| | 00:55 | First is to just get audio playing in the
background, which it won't do right now.
| | 01:00 | Right now, if I press the Home
button, it will just stop playing.
| | 01:03 | But second, even after that's done, I
also want to allow our app to respond to
| | 01:08 | the Play and Pause controls that the
iPhone will give us when it's in the locked
| | 01:12 | or in multitasking mode.
| | 01:14 | These are referred to as Remote Control Events.
| | 01:17 | We want to allow a different UI to send events
to us that we'll respond to. So what do I do?
| | 01:23 | Well, when I am setting up this player
in my viewDidLoad, there are a couple of
| | 01:28 | other things I am going to add.
| | 01:29 | So, after I get done with the player, I
am going to add a line here to set the
| | 01:33 | category of the audio.
| | 01:35 | And we do this by calling a method of
AVaudioSession, sending our category to
| | 01:41 | AVaudioSessionCategoryPlayback.
| | 01:43 | Now there are several
available types of category.
| | 01:46 | We've got Ambient, we've
AudioProcessing, PlayAndRecord.
| | 01:49 | And you might think that the one we
need is Ambient, because we are going to
| | 01:53 | play an Ambient noise, but it's really
more about, what is the application doing?
| | 01:58 | For example, if it was doing voiceover
IP, we might need PlayAndRecord.
| | 02:02 | These choices are more about, does the
iPhone silence other audio that might be
| | 02:08 | playing, or attempting to
play, while your app is running?
| | 02:10 | But what I am going to use is
AVaudioSessionCategoryPlayback, a very common one to do.
| | 02:16 | Then after that, I do need to one more
thing here, which is I am going to say
| | 02:21 | that I want to receive those Remote
Control Events, that either the iPhone can
| | 02:26 | give us when it's locked or in the double
-clicked Home button Multitasking mode,
| | 02:32 | so we call
beginReceivingRemoteControlEvents there.
| | 02:35 | But we're not actually doing that because there
are a couple of other things we need to first.
| | 02:40 | So after viewDidLoad is done, I am
going to add a little code in here, to first
| | 02:44 | say, canBecomeFirstResponder.
| | 02:47 | We're wanting to make sure that our
application is announcing itself to the
| | 02:52 | world and saying, yes, I can do this.
| | 02:53 | I can respond to events that the
user might do by touching the screen.
| | 02:58 | We're then going to provide a little
note here saying, not only can I do that, I
| | 03:03 | want to do that. So in viewDidAppear
I'm calling self BecomeFirstResponder.
| | 03:08 | This call wouldn't really work if I did it
in viewDidLoad; that's a little too early.
| | 03:12 | So viewDidAppear is good.
| | 03:14 | We're saying we're supporting being
first responder, and then we are becoming
| | 03:17 | the first responder.
| | 03:18 | After that, what I am going to do is support the
| | 03:21 | remoteControlReceivedWithEvent, so
that those remote control events from the
| | 03:27 | Pause/Play button can be sent through to us.
| | 03:30 | And I am going to ask what is the event Subtype?
| | 03:33 | There are actually multiple event
subtypes that in am going to use.
| | 03:35 | The most common one here for
audio is this one, which is the
| | 03:39 | UIEventSubtypeRemoteControlTogglePlayPause.
Bit of a mouthful there.
| | 03:45 | And the thing that I want to show
here is there are multiple ones here:
| | 03:48 | RemoteControlStop,
RemoteControlNextTrack, BeginSeekingForward,
| | 03:52 | BeginSeekingBackward.
| | 03:54 | And we do have RemoteControlPause and
RemoteControlPlay, but bear in mind the
| | 03:59 | usual interface, when the iPhone
is locked, is it's the same button;
| | 04:03 | it's a toggle button, not an
individual Play and Pause button.
| | 04:06 | So we want the TogglePlayPause here.
| | 04:09 | That should do it for our code, but there
is one super-important thing that we have
| | 04:14 | to do here to actually allow our
application to support playing audio in the
| | 04:19 | background, and it's nothing do with code.
| | 04:21 | What we need to do is go in to our
plist file, and I am going to add in a
| | 04:26 | new entry, so I am going to click on the last
row here and then just click the Return key.
| | 04:31 | And the one that I want to find here
is the Required background modes key.
| | 04:37 | I am going to select that.
| | 04:39 | It's actually an array, so after I
have clicked off, I'll get the little
| | 04:42 | disclosure triangle over here.
| | 04:43 | I need to expand it.
| | 04:44 | And it can have multiple items, because
in each of the elements of the array, we
| | 04:50 | can say either the App plays audio,
| | 04:52 | it registers for location
updates, or provides voiceover IP.
| | 04:56 | We could do three entries and
support all three of these, but I am only
| | 04:59 | interested in one, which is the App plays audio.
| | 05:02 | So I am going to save the Property
list file and build this application.
| | 05:07 | It says Build Succeeded.
| | 05:09 | Now I could go ahead and just click
Build and Run, but unfortunately it would do
| | 05:12 | me very much good, because this
behavior is not supported in the simulator.
| | 05:18 | So I am going to have to run it on
my device and just take a couple of
| | 05:22 | screenshots to kind of show the
impact that this is going to have.
| | 05:25 | So I am going to select the device
from the dropdown and say Build and Run.
| | 05:32 | It takes a few seconds and
installs on my physical iPhone device.
| | 05:36 | I am going to press Play to make
sure it's working, which it seems to be
| | 05:40 | working just fine.
| | 05:42 | Then I am going to hit the Home button.
| | 05:45 | And right now, this is actually working.
If I want to take a quick screenshot of the device,
| | 05:50 | I have this running, but I
have gone back to the Home button.
| | 05:52 | If I double-click to move into
Multitasking mode, I'll actually see it running
| | 05:58 | in the background, but
the audio was still playing.
| | 06:00 | This is a fairly short app, so I am
going to back to it, press Play again.
| | 06:06 | It's only got 30 seconds of audio.
| | 06:10 | And then I am going to move into the
Multitasking mode with the Pause keys. Press Pause.
| | 06:16 | It pauses my audio.
| | 06:18 | Get ready to press Play again, and
it will play again and keep on going.
| | 06:25 | And even the Remote Control Events on
the actual locked screen will send its way
| | 06:30 | through to the application as well.
| | 06:33 | So this is the core of what we have to do.
| | 06:35 | Probably the single most important thing
is this actual item in your Property list,
| | 06:40 | that App plays audio as a background mode.
| | 06:43 | But after that, we are making sure
that we set the category of the audio
| | 06:48 | properly, because if you don't,
you may say that you are supporting
| | 06:51 | background audio, but it's not
counting it as the right kind of category to
| | 06:55 | actually play in the back ground.
| | 06:57 | And then the optional but very useful
ability to begin receiving Remote Control
| | 07:01 | Events and then reacting to those events,
and that allows you to tap into this
| | 07:05 | whole audio multitasking stuff.
| | Collapse this transcript |
| Upgrading existing projects| 00:00 | Let me show you one way I might convert
an older, non-multitasking app into an
| | 00:05 | app that supports multitasking.
| | 00:07 | I'm going to open up a simple application I
actually created in my first iPhone SDK course.
| | 00:12 | This was an app that demonstrated how to
load application settings that had been
| | 00:17 | changed using the Settings
app on the iPhone itself.
| | 00:20 | Now right now, it's to going to fail, because
up here I see that I've got Base SDK missing.
| | 00:25 | Now this will depend a little bit on
what versions of the SDK you have installed.
| | 00:29 | You can always check by holding down
your Option key and clicking here to see
| | 00:33 | what I've got, but unfortunately as I
can see here, I'm currently compiling
| | 00:37 | against the iPhone 3.0, which is missing.
| | 00:40 | I don't have that SDK anymore. Not a problem.
| | 00:42 | I want this to be
supporting multitasking anyway.
| | 00:45 | So I do want this to go against the 4.0.
| | 00:48 | So I'm going to right-click or
Ctrl+click the actual name of the project itself
| | 00:52 | and go to Get Info, where I then want
to make sure I'm on the Build section of
| | 00:57 | my information about the project.
| | 01:00 | Down here where it says the Base SDK is
3.0, which is missing, I can either go to
| | 01:05 | 3.2, that would really be relevant for
the iPad, or in our case a 4.0, and that
| | 01:09 | should do the trick.
| | 01:11 | Now right now, up here it's actually
going against the device. I want it to go
| | 01:14 | against the simulator for right now.
| | 01:16 | So if I save that and then go ahead to
my Build menu and just say Build and Run,
| | 01:20 | it's not very exciting.
| | 01:22 | In fact, you're not
really seeing very much here.
| | 01:25 | Let me just scale this up a little bit.
| | 01:28 | It's actually trying to load in some
information from the Settings application,
| | 01:33 | such as name and a background color.
| | 01:35 | Now if I look at that Settings
application, I'll see that I have a section here
| | 01:41 | for this app, called SettingsSave,
where I can put in a name, such as Simon, a
| | 01:47 | Large Font toggle, whether it's on or
off, and down here that actually is a
| | 01:51 | Favorite Color, so Green, for
example. I'm going to back.
| | 01:54 | Come out of the Settings
and reload this application.
| | 02:00 | Unfortunately, those new
settings are not being loaded.
| | 02:03 | So what's going on here?
| | 02:05 | Well, the deal is that right now I
do have the right kind of code that's
| | 02:11 | actually going against the defaults
database and loading in these settings that
| | 02:16 | have been specified there.
| | 02:17 | Now if you haven't done this before,
the way that you really specify external
| | 02:21 | settings is you can do it in your
resources, what's called your Settings.bundle.
| | 02:29 | We have a series of items and a
property list that name, for example, the fact
| | 02:34 | that we've got a ToggleSwitch called a
Large Font that has a key of big_font.
| | 02:39 | Now this is not what I'm
trying to explore right now.
| | 02:42 | What I'm really interested in is the
idea that in the ViewController, this code
| | 02:47 | has been written to load in those
preferences, but it's only happening in
| | 02:51 | viewDidLoad, and that was perfectly fine
if our application started from scratch
| | 02:56 | every time, but it's not
fine if we're multitasking.
| | 02:59 | We'll never pick up any changes.
| | 03:00 | So this code will never be called,
except the first time the program loads.
| | 03:04 | So I think, well, I do still want
this code to be called the first time
| | 03:09 | the program loads, but I also want it called
whenever the app will enter the foreground.
| | 03:15 | So what I'm going to do is just take
this Settings code here and extract it out
| | 03:20 | of viewDidLoad, taking everything
but the call to super viewDidLoad.
| | 03:26 | I'll just create a simple method right
here, called loadSettings, and paste in my
| | 03:35 | settings code here, so we can
call this a couple of times.
| | 03:37 | So in fact, what I'll do is when
viewDidLoad is called, I will just say, self
| | 03:42 | loadSettings, and then what I want to
do is make sure that that code will also
| | 03:48 | be called whenever I'm going to restore
this application from the background, so
| | 03:54 | whenever it's moving to the foreground.
| | 03:56 | Now normally what we'd expect to see
is our ApplicationWillEnterForeground
| | 04:00 | method, but that's in the
application delegate, and I want to do this in
| | 04:03 | the ViewController.
| | 04:04 | So what I'm going to do instead is I'm
going to set up a notification center,
| | 04:08 | and I'm going to say I want to be
notified when that notification happens, when
| | 04:12 | the application will enter foreground.
| | 04:15 | The way that I'm going to do this is
just create an NSNotificationCenter.
| | 04:19 | So I create an NSNotificationCenter, the
default center, the usual way of creating this.
| | 04:25 | Then I'm adding an observer to this class.
| | 04:27 | I'm saying that my selector is the
method called loadSettings, and the
| | 04:31 | notification I'm interested in is the
| | 04:33 | UIApplicationWillEnterForegroundNotification.
| | 04:37 | Now because I'm now formally
subscribing this loadSettings method to that
| | 04:43 | notification, I do have to make sure
that the method's signature up here can
| | 04:48 | take an NSNotification object,
| | 04:51 | even if in this case I'm not really
going to do anything with it, but I need
| | 04:55 | that signature to match.
| | 04:57 | So we've extracted this code out
that will read those settings and change
| | 05:02 | the user interface.
| | 05:04 | We'll call that once when we do viewDidLoad.
| | 05:07 | Just to make sure that I can call it
properly, I'll just pass in a parameter
| | 05:11 | of nil, because it's expecting something.
| | 05:14 | Then I'm subscribing to that
UIApplicationWillEnterForegroundNotification.
| | 05:16 | Well, let's see what happens.
| | 05:21 | I'm going to save this, compile
this code, build has succeeded.
| | 05:24 | I'll click Build and Run.
| | 05:26 | Because I'm using a small resolution, it's
forcing the iPhone simulator down into 50%.
| | 05:31 | If I want to scale it back up, I can do that.
| | 05:35 | I can see that I seem to be reading
something from the Preferences pane, but if
| | 05:38 | I exit out of that, I'm going
to go back to the Settings pane.
| | 05:43 | I'll say change the Favorite Color to Blue.
| | 05:47 | Come back here and change
the name to Second Test.
| | 05:53 | Come out of Settings. Go back over.
| | 05:56 | Load it back up, and what happens?
| | 05:58 | Well, we were expecting to see blue,
we're expecting to see a different name here.
| | 06:02 | So what's the problem?
| | 06:03 | Well, the issue here can often be that
I'm doing it a little bit too quickly.
| | 06:09 | When I'm in these Settings application,
and I'm actually reaching in here and
| | 06:13 | changing some of this information,
| | 06:16 | it's affecting that settings database,
what's called the defaults database.
| | 06:21 | The issue is, at the back here in
my code where I grab hold of that
| | 06:25 | NSUserDefaults object, it might be just
a little bit out of sync with what's
| | 06:30 | actually been set in that Settings
application, simply because different
| | 06:34 | applications can reach in
there and read and write to it.
| | 06:38 | So what I'm going to do is after I
grab that object called defaults, I'm going
| | 06:43 | to call synchronize, and it will force it to
re-sync and hopefully grab the latest data.
| | 06:47 | We'll test that again. So I'll run this.
| | 06:52 | It's picking up those new
settings of the blue background.
| | 06:56 | Let's just do a simple check that I
could go into Settings, change the
| | 07:00 | background to green, come back out,
reload the app, either by selecting it
| | 07:07 | from the springboard or double-
clicking here and selecting from the
| | 07:11 | multitasking part.
| | 07:13 | You see that it immediately
changes to the green background.
| | 07:16 | In fact, if you'd watch very closely,
you would have seen kind of a bit of a
| | 07:19 | flash there, that the app
seemed to zoom in and then changes.
| | 07:23 | It zoomed in on blue, and then changed to green.
| | 07:26 | That's because the iPhone took a
screenshot of the app in a blue state when it
| | 07:31 | went to the background, and it was
using that screenshot to animate the
| | 07:34 | application before it then changed the
user interface. Not really a big deal.
| | 07:39 | I'm obviously doing a very, very
significant change to the UI, which would
| | 07:42 | be unusual in most applications between going
to the background and going to the foreground.
| | 07:48 | So in this particular example, I didn't
really worry too much about saving state
| | 07:53 | and using the
ApplicationDidEnterBackground method, as I didn't really care.
| | 07:58 | All my changes were really about that
| | 08:00 | ApplicationWillEnterForegroundNotification,
because really the state of my
| | 08:05 | application is being saved from the outside.
| | 08:07 | It's being saved in that Settings
application, but of course, if you're
| | 08:10 | more interested in the saving state,
then you can either use the
| | 08:15 | ApplicationDidEnterBackground
method in your app delegate or if you're
| | 08:18 | interested in calling it from say
your ViewController, you can use the
| | 08:22 | NotificationCenter to subscribe to that
notification and do that exactly the same way.
| | 08:28 | Now we have an application that's
gone from the old, very incompatible 3.0
| | 08:33 | version of the SDK with just a few
simple changes, ready for the 4.0 SDK.
| | Collapse this transcript |
|
|
3. Using the Event Kit FrameworkIntroducing the Event Kit framework| 00:00 | In iOS4, we now have a programmatic way
to access events in the built-in calendar
| | 00:06 | database, and even to edit or create
those events from our own applications.
| | 00:11 | Now, to do this, we have two new
frameworks, but luckily they're both pretty small.
| | 00:15 | We have EventKit, which contains the
classes we used to programmatically work
| | 00:20 | with events, and we have EventKitUI,
which is a very small framework; it's really
| | 00:25 | only two view controllers.
| | 00:28 | This is there because Apple don't want
1,000 different ways to display or edit a
| | 00:32 | calendar entry, so they're providing a
couple of user interfaces for doing this.
| | 00:37 | Now, Apple aren't intending these
frameworks to be used to make a full-fledged
| | 00:42 | replacement for the Calendar app.
| | 00:44 | They're just trying to provide what's
often been requested, a way to make an
| | 00:47 | appointment or alarm or to
check existing events for a user.
| | 00:52 | That's really the big issue here:
what do you actually want to do?
| | 00:55 | Do you want to get a range of events,
| | 00:58 | say, tell me what's coming up in the next
week or the next month or the next year?
| | 01:02 | Do you want to create an event?
| | 01:04 | Now, bear in mind, if you use these
frameworks to create an event from your
| | 01:08 | application, and the calendar on the
device is linked to, say, an exchange server,
| | 01:13 | the event you create will sync back to
that server, just the same way as if the
| | 01:17 | user had manually created the
event using the iPhone Calendar App.
| | 01:22 | You can, of course, edit or delete
events, and because the user's calendar is
| | 01:27 | often synced to an external source, the
events could also be being updated while
| | 01:32 | your app is running, so you can also
register to be notified of changes, so that
| | 01:38 | your app always has the
most up-to-date information.
| | 01:41 | So, we're going to explore a couple
of these different options for using
| | 01:45 | EventKit. Now there's one thing to keep in mind.
| | 01:49 | Technically you could use EventKit to
create a whole bunch of events as soon as
| | 01:54 | your application begins.
| | 01:56 | However, there is an official message
from Apple, don't ever create an event
| | 02:01 | without telling the user, whether
that's using the Apple UI for adding an
| | 02:06 | event, or whether you are popping up an
alert or a model-View-Controller, there
| | 02:10 | needs to some interaction that the
user gets to say yes or no, this event is
| | 02:14 | actually being created.
| | 02:16 | So, while you could do it
programmatically, Apple are likely to bounce your
| | 02:20 | app out of the app store if they find you're
secretly creating events in the background.
| | 02:25 | So, let's go ahead and see how to
work with the classes in EventKit.
| | Collapse this transcript |
| Creating calendar events programmatically| 00:00 | Although it's recommended that you
use the provided Event Kit UI view
| | 00:05 | controllers to create, edit, and
display your calendar events, let's first see
| | 00:10 | how to create those events purely
programmatically, using the Event Kit framework
| | 00:14 | to get a handle on how it works.
| | 00:15 | So, I have a very simple project here,
and it's just got one button on this
| | 00:20 | view-based application that itself
links to an IBAction method called
| | 00:25 | createEvent, and I'm
going to add everything here.
| | 00:28 | Well first off, if I do want to work with
the Event Kit framework, I better link to it.
| | 00:32 | So I'm going to come to my
Frameworks folder, right-click and say Add >
| | 00:37 | Existing Frameworks.
| | 00:38 | I'm going to find the EventKit.framework.
| | 00:41 | We don't need EventKitUI
right now, and click Add.
| | 00:45 | Next, in this ViewController here, I'm
just going to put an import statement
| | 00:49 | to tell it that yes, we want to use
EventKit.h, and now I can go ahead and
| | 00:56 | start working with this. So, what do we do?
| | 00:58 | Well, I'm going to create the event
in here, and there's several steps that
| | 01:03 | we need to work this.
| | 01:04 | First off, we need to grab
something called the event store object.
| | 01:08 | This is really creating an object that
allows us to connect to that calendar database.
| | 01:14 | It's called the EventStore.
| | 01:16 | Using the EventStore, we can then create
events, we can save events, we can edit
| | 01:20 | events, all sorts of
things, but that's step one.
| | 01:23 | Next, we want to actually
create a new event object itself.
| | 01:27 | It's the EKEvent, and we're using that
EventStore that we created on the previous line.
| | 01:33 | Now, if I'm defining an event, I do
have to have a couple of fields that are
| | 01:37 | essential, things like the
startDate and the endDate and the title.
| | 01:41 | So what I'm going to do first is just
create a couple of date objects to hold
| | 01:45 | the start date and end date.
| | 01:47 | For this example, I'm just going to set
them both to today, this date right now,
| | 01:51 | because I'm actually going to make this
event an all-day event, so we can keep
| | 01:55 | it as the same date for start and end.
| | 01:58 | Now that I have those,
| | 02:00 | I can go ahead and start setting some
properties of this new event object.
| | 02:04 | There are certain things that
are required, such as the title.
| | 02:08 | We need to give it a start date, we
do need to give it an end date, and I'm
| | 02:11 | also going to set the property of
allDay, which is a Boolean, and we're just
| | 02:15 | going to set it to Yes.
| | 02:17 | Now, one of the questions you might have
is well, where does this event get created?
| | 02:21 | Well we haven't saved it yet, but we do
also have to tell the event, well, you
| | 02:26 | might have multiple calendars to deal
with, so we're first going to tell it that
| | 02:31 | the events calendar should be whatever
the default calendar is on the device.
| | 02:36 | So, if it's set to connect to
Exchange, it's going to be that one;
| | 02:39 | if it's set to connect to
calDAV, it's going to be that one.
| | 02:43 | You might only have one calendar in
your calendar app, but this is the idea of
| | 02:47 | explicitly saying we're saving
to whatever the default one is.
| | 02:50 | Now, before we save this event, one
thing that I do need to do is create a
| | 02:55 | pointer to an NSError object.
| | 02:57 | The reason that I do this is that on
the following line, I'm actually going to
| | 03:01 | save the event, and the method that
we call is on the eventStore object at
| | 03:06 | saveEvent colon, span colon, error.
| | 03:09 | saveEvent means that we are actually
just passing the event that we have created
| | 03:14 | and set all the properties of in the
previous lines, span is something that's
| | 03:18 | really not that important for this event.
| | 03:21 | It's really more impactful when we're
working with events that have recurrences,
| | 03:26 | and I want to say that if I'm perhaps
editing one, I either want to edit just
| | 03:29 | this event, or I want the span to
cross multiple recurring events.
| | 03:34 | For right now, I can just put in the
EKSpanThisEvent, and then we're passing the
| | 03:39 | pointer to the NSError that we
just created in the previous line.
| | 03:42 | So if there is a problem when creating it, that
error object can actually have some values in it.
| | 03:47 | Now this line should do it.
| | 03:50 | It should actually save to that
calendar database, to the event database.
| | 03:54 | The question is we probably want to do
a bit of tightening up here and actually
| | 03:58 | ask, well, did we get an error come back?
| | 04:00 | And if we did, we of course could deal with it.
| | 04:02 | I'm going to make the assumption that
we didn't, so I'm just going to do a
| | 04:05 | little check for that, test for errors.
| | 04:08 | If we have no errors, I'm going to
create a new UIAlertView object with just
| | 04:13 | some messages that event was created,
how about that, and a button they can
| | 04:16 | dismiss. And the real reason I'm
doing this here is that Apple strongly
| | 04:20 | recommend that you never create events
programmatically without giving some kind
| | 04:25 | of feedback to the user, and this is
very much very obvious feedback, but we're
| | 04:30 | following the spirit of the law, if
not the letter, so
| | 04:33 | we'll create that, we'll show the alert,
and then after it's shown and the user
| | 04:37 | dismisses it, we can
release it.
| | 04:40 | Well, seeing as we're really done with
the other objects we've created too, we
| | 04:44 | can release those as well, which are the
startDate and endDate and actually the
| | 04:48 | eventStore itself for the ones that we
used alloc and init with.
| | 04:52 | So, I'm going to save this, I'm
going to build it, and see what happens.
| | 04:55 | The build has succeeded,
and now I'm going to run it.
| | 04:57 | Well, there's the question, what do I do here?
| | 04:59 | Well, unfortunately if I use the
simulator, we're going to be in for a bit of a
| | 05:03 | problem, because the
simulator doesn't have a calendar app.
| | 05:07 | So there's not really an easy
way to check whether this worked.
| | 05:10 | So, instead, I am going to run this
on the device, I'm going to click Build
| | 05:14 | and Run, and I'm just going to take a
couple of screenshots to prove that this
| | 05:18 | is going to work here.
| | 05:20 | So, what you can't see it, the app
is actually running on my device.
| | 05:23 | What I'm going to do is just open up
my Organizer window, which right now is
| | 05:27 | pointing to my iPhone 4 that I have,
and I'm going to take just a quick
| | 05:31 | screenshot, I'm going to capture that,
which is the app with the Create Event
| | 05:35 | button, I'm going to tap that button,
going to do another capture, and you can
| | 05:39 | see that it's popping up a message that
says the event has been created, and the
| | 05:43 | question might be, well, go prove it.
| | 05:45 | Well, I'm going to dismiss that, I'm
going to leave the application, go over
| | 05:48 | to my calendar app, do another
capture, and I can actually see that on my
| | 05:53 | month view there I've got the all-day Title
for new event has actually been added on there.
| | 05:59 | So it's reached into my
calendar database and added that event.
| | 06:04 | Now, as we'll see, the recommendation,
and that's a strong recommendation from
| | 06:08 | Apple, is that if you're generally just
adding events one at a time, you probably
| | 06:12 | want to use the standard user
interface elements that Apple provide, but
| | 06:17 | certainly if you want to say create
multiple events and you're just popping up a
| | 06:21 | message to the user asking if this is
okay, this is certainly one way you could
| | 06:26 | go about doing it, is just
programmatically adding them.
| | Collapse this transcript |
| Using the Event Kit UI components to add and edit events| 00:00 | So now let's see how to create an
application that actually uses the EventKit UI
| | 00:04 | view controllers to allow us to create events.
| | 00:08 | Once again, I'm back to this
idea of a very simple, very
| | 00:11 | straightforward application.
| | 00:12 | It's got one button on it right now
that is calling an IBAction, and there's no
| | 00:17 | code in that, and we're
going to create the event there.
| | 00:20 | Well, we're using both EventKit and
EventKit UI here, so I am going to just add
| | 00:26 | a link to those frameworks.
| | 00:27 | Let's do that second one.
| | 00:36 | So I have both EventKit and
EventKitUI showing up in our Frameworks folder.
| | 00:41 | Next, I want to do an import statement.
| | 00:44 | And EventKitUI is really the only
thing that I have to use in my code here.
| | 00:52 | So, what do I do in my IBAction?
| | 00:54 | Well, I am going to start off
with the same eventStore object;
| | 00:58 | we need to grab hold to this object to
represent the link to that event database
| | 01:04 | on the backhand here.
| | 01:05 | But instead of creating an event object
programmatically, we're going to create
| | 01:10 | the EditViewController.
| | 01:13 | This is part of EventKitUI and looks
almost identical to the built-in calendar app.
| | 01:18 | In fact, you'd be hard-
pressed to tell the difference.
| | 01:21 | So we create the controller, we're
going to set its eventStore to the
| | 01:25 | eventStore object I just created,
we're then going to set the controller's
| | 01:30 | editViewDelegate to self, to this
ViewController object. That does mean that
| | 01:34 | we'll have to do something to respond
to that in a moment, and it's really
| | 01:37 | because we're going to pop open this
ViewController, and we need it to be able
| | 01:41 | to notify us when it's done.
| | 01:44 | I'm going to present that
ModalViewController as animated.
| | 01:47 | That's the classic way of making this
appear, so that your users will be
| | 01:51 | comfortable with seeing it, and it looks
like the standard iPhone calendar event
| | 01:56 | creator, and then I'm going to release that.
| | 01:59 | Now, really the only thing that might
take a little bit more explanation is this
| | 02:03 | idea here, the idea of the editViewDelegate.
| | 02:05 | What does that mean?
| | 02:06 | Well, it actually does mean that we
need to create a delegate method here.
| | 02:10 | Now I'm going to write it right
now, which is the delegate method for
| | 02:13 | EKEventEditViewDelegate.
| | 02:16 | Let me just split that onto a couple of
lines here, can see it, and all that it
| | 02:21 | is the eventEditViewController colon
did complete with action, and we're going
| | 02:27 | to just dismiss the ViewController away.
| | 02:30 | However, because I am saying
I'm being the delegate for the
| | 02:32 | EKEventEditViewDelegate, I do need to
go and officially support that in my
| | 02:39 | header file, so I'm going to go back
to my ViewController header file over
| | 02:42 | here, and say that yes, I'm going to
support that EKEventEditViewDelegate.
| | 02:49 | That's what I'm going to do.
| | 02:50 | Now, the issue is if I compile this,
it's probably not going to know what that
| | 02:54 | is right now. Can't find it.
| | 02:56 | So, I do need to add an import
statement over here, and build succeeded.
| | 03:05 | It understands the delegate
protocol, and then over here we have all the
| | 03:10 | necessary code to do it.
| | 03:12 | So, let's see how this one works.
| | 03:13 | Now, just to show the effect of it, I
am going to run this on the simulator,
| | 03:18 | click Build and Run. Because I have a
small resolution, it will pop up at 50%,
| | 03:24 | but I'm going to just scale that up
a little bit, and click the button.
| | 03:30 | You see how what is popping up
here is essentially identical to the
| | 03:34 | built-in calendar app.
| | 03:36 | We have the ability to add a Title, and
a Location, we can say whether it starts
| | 03:44 | and ends, we can set whether it's repeating.
| | 03:47 | We can even set an Alert here.
| | 03:50 | And then I can say Done, and the actual
view controller itself will take care of
| | 03:55 | adding that event to our calendar.
| | 03:58 | I don't have to manually, or
programmatically, create the event object itself.
| | 04:05 | So with just a few lines of code, you
can see how we can use the built-in user
| | 04:09 | interface view controller to very
simply support an almost identical behavior
| | 04:14 | within our own applications
to the built-in calendar app.
| | Collapse this transcript |
|
|
4. Blocks and Grand Central DispatchIntroduction to blocks| 00:00 | A new language feature in iOS 4 is
blocks, and we use blocks to group our code
| | 00:05 | together into reusable chunks.
| | 00:07 | Now you might first hear that
description and think, okay, and then a few
| | 00:11 | seconds later think, but surely that's
what we do with functions and methods.
| | 00:16 | Yes, functions and methods do allow
us to group code together into reusable
| | 00:19 | chunks, and there are similarities, but
blocks are used for different reasons.
| | 00:24 | So, why do we use them in the first place?
| | 00:25 | Well, the first and main reason
is that you are going to have to.
| | 00:29 | Many of the new and the
updated APIs in iOS 4 use Blocks.
| | 00:33 | If you want to use the Assets Library,
Grand Central Dispatch, new audio and
| | 00:37 | video features, the Game Kit,
| | 00:40 | you are going to have to use these.
| | 00:41 | Blocks as not some weird
esoteric feature that you get to ignore.
| | 00:46 | If you are going to work with iPhone
development, you'll be about as able to
| | 00:50 | avoid blocks as you would be
able to avoid using integers.
| | 00:54 | We are going to see and use blocks
several times in later parts of this course.
| | 00:58 | Now, the other reason is
that they make your life easier.
| | 01:02 | Blocks allow you to write less code,
more readable code, and easily allow you to
| | 01:06 | tap into powerful techniques in iPhones
development; things like multithreading
| | 01:10 | and callbacks become so
much easier using Blocks.
| | 01:15 | This is why blocks are
common in other languages.
| | 01:18 | In fact, Objective-C is a
little late to this party.
| | 01:21 | Other languages have had blocks or their
equivalents for years often under names
| | 01:26 | like anonymous functions or closures or lambdas.
| | 01:29 | Well, this is all a little
bit out there, but abstract.
| | 01:33 | So how do we actually do this?
| | 01:34 | Let me show you a simple example of the syntax.
| | 01:37 | We are talking about blocks being
reusable code chunks, and let's take one
| | 01:43 | of the existing ones.
| | 01:44 | Let's say we've got a function - and this has
nothing to do with the Block right now -
| | 01:47 | a typical function. It's got a function
that's created, it takes a variable, it
| | 01:51 | gets returned, and it's called
by some other piece of our code.
| | 01:55 | Now, that function itself is going to have
the return type, in this case an integer;
| | 02:00 | it's going to have a name, in this
case myFuntion; and it's going to take
| | 02:03 | arguments, in this case one integer.
| | 02:06 | Then we've got the body of the
function, which could be one line.
| | 02:09 | It could be a dozen lines.
| | 02:11 | it could be a thousand lines.
| | 02:12 | So, this is your common function, and
when we think about a reusable chunk of
| | 02:17 | code, we think about something like
this: either a function or a method.
| | 02:22 | If we want to see its direct equivalent
using blocks, we'll see something like
| | 02:27 | this, and you'll quite commonly see
this shown as an introduction to blocks.
| | 02:31 | I don't think it's all that
helpful, and I'll explain why.
| | 02:33 | Yes, a block can be created like this where
we actually have the official return type here.
| | 02:39 | We have the name of the block -
| | 02:40 | in this case we are calling it myFirstBlock -
and it's got the caret symbol before it.
| | 02:45 | We'll talk about that in a second.
| | 02:47 | Here, we've got something that says
arguments, but in fact, in this particular
| | 02:51 | format, we'll have another
section that say arguments.
| | 02:54 | It doesn't look like we've
really won anything. It's more code.
| | 02:58 | It's kind of slightly more annoying
to look at because of the caret symbol.
| | 03:03 | The issue is when you often see the
full declaration of what a block can be,
| | 03:08 | it's giving you a lot of optional stuff:
| | 03:11 | things that you would have to do for a function,
but you actually don't have to do for a block.
| | 03:15 | One of the things that we are actually
looking at here is really two blocks,
| | 03:19 | which is why you are seeing two carets.
| | 03:21 | Effectively, we are seeing the first
part here being a block variable, that we
| | 03:25 | are naming it, and we are saying it
has a return type, and it has parameters.
| | 03:31 | But on the right-hand side of the Equal
sign, we are seeing the block literal, which
| | 03:34 | is what is the block?
| | 03:37 | In fact, the block literal part
is the most important part of this.
| | 03:41 | An analogy I can make is, say you are
working with string literals where we can
| | 03:45 | do something like this, where we can
create a string variable, NSString *message
| | 03:51 | and set it to the values of a string literal.
| | 03:53 | But for the most part, we are going to
use string literals, and we find string
| | 03:57 | literals more useful, when we can just use them.
| | 04:01 | We call NSLog just
passing in the string literal;
| | 04:04 | we didn't have to make a variable based on that.
| | 04:07 | In the same fashion, if we go back to
our block declaration, while this is the
| | 04:11 | official full declaration of a block,
what we can often do is just dump the
| | 04:17 | first part of it, and say we don't
actually need the block variable.
| | 04:20 | We are just going to use
the block literal directly.
| | 04:23 | Now, this will typically be embedded in
some other code, but if you are seeing
| | 04:27 | that caret symbol, the first symbol
that we see here, on a US or UK keyboard,
| | 04:33 | this is found by holding
Shift and the number 6, Shift+6 -
| | 04:36 | if you are using a different
international keyboard, you may have to hunt for
| | 04:40 | it - but the caret symbol is
what tells us this is a block.
| | 04:44 | Once you see this symbol,
you know you've got a block.
| | 04:47 | So, your first exposure to blocks is
likely to be seeing them being used in some
| | 04:52 | call to one of the new API features.
| | 04:55 | This, for example, is a call to a new
method in the Assets Library Framework,
| | 05:01 | and the entire thing is just really
one statement, one line here, calling
| | 05:05 | assetslibrary enumerateGroupsWithTypes, and
it's actually using two blocks here as arguments.
| | 05:11 | The first one is usingBlock call on, and
then the block just begins with caret symbols.
| | 05:16 | It says it takes two parameters, and
then within the opening and closing braces
| | 05:21 | we can have a bunch of code written here.
| | 05:24 | Then there is a second one here
being passed as this last argument.
| | 05:28 | This is a failureBlock.
| | 05:29 | What are we actually doing?
| | 05:31 | Well, what this allows to do is, inline,
when we are calling this method, we can
| | 05:36 | say, what happens when this method is called?
| | 05:40 | If you've ever found yourself having to
go and create an external method, name
| | 05:45 | it, and then pass the name of that
method as a selector to yet another method,
| | 05:50 | that's a good place where
you might want to use a block.
| | 05:53 | What this would allow us to do here is
write, inline, the code that we want to
| | 05:58 | happen in different conditions to this
method call, instead of worrying too much
| | 06:02 | about callbacks and all sorts of stuff.
| | 06:04 | It makes it much easier to read and
understand by keeping your code inline.
| | 06:09 | Now, while blocks can be passed
directly as arguments just by using that caret
| | 06:14 | symbol where it's permissible, you
can also create blocks as objects.
| | 06:18 | They can be created, and they can be named.
| | 06:21 | In this example, what I am doing here is
creating a block, and there is actually
| | 06:25 | a type here, called the
ALAssetsLibraryGroupsEnumerationResultsBlock, which name
| | 06:30 | just rolls off the tongue there.
| | 06:31 | But what you'll find is a lot of the
new APIs in iOS have their specific
| | 06:38 | blocks actually named.
| | 06:39 | So, I am creating one called
listGroupBlock, I am setting =^.
| | 06:45 | This takes two parameters.
| | 06:46 | I am going to put in whatever code I want
to be called when this block is executed.
| | 06:52 | I am then going to create another block
here, called failBlock, and this is what
| | 06:57 | might happen when an error occurs.
| | 06:59 | When this code is written, and when
this code is actually first executed,
| | 07:04 | nothing happens, except the
blocks are created and defined.
| | 07:08 | So, I'll now have something called
listGroupBlock, and I'll have something
| | 07:12 | failBlock, and a little later on in my code
| | 07:15 | what I can then do is make another
method call and actually pass in these blocks
| | 07:20 | in as arguments to that method call.
| | 07:23 | Now, you'll find you first need to
learn blocks, so that they you can use them
| | 07:28 | to pass into methods and
newer updated frameworks in Cocoa.
| | 07:32 | Now, as you get more comfortable with
blocks you'll likely to create even your
| | 07:35 | own methods that take blocks as arguments.
| | 07:38 | I find most people learn blocks best by
just starting to use them, recognizing
| | 07:44 | the caret symbol as the immediate red light
that goes off that says, here I've got a block.
| | 07:49 | Then rather than going deeper into the
syntax immediately, which if you'd like
| | 07:53 | to, you can do yourself by looking
at the Apple developer documentation,
| | 07:57 | we are next going to explore how to
use these blocks with one of the new
| | 08:01 | features in iOS4, something
called Grand Central Dispatch.
| | Collapse this transcript |
| Introduction to Grand Central Dispatch| 00:00 | Grand Central Dispatch, or GCD, is
something new in iOS 4 for iPhone developers.
| | 00:06 | If you read the official documentation
from Apple about it, you'll see all sorts
| | 00:10 | of phrases that it provides systemic,
comprehensive improvements in the support
| | 00:14 | for concurrent code execution.
| | 00:16 | At the end of the day, we use Grand
Central Dispatch because it makes
| | 00:20 | multithreading easy.
| | 00:22 | Your question right back on this
might be, well, why do we care that
| | 00:25 | multithreading is easy?
| | 00:27 | The end result, no matter how you
spin it, is that we want to have a more
| | 00:31 | responsive user interface.
| | 00:33 | We want our applications
to feel more professional.
| | 00:36 | Really, what we're trying to do is make
the apps able to do multiple things at once.
| | 00:40 | Now, of course, here we're talking
about multithreading, not multitasking.
| | 00:45 | Multitasking, which we have in iOS
4 as well, is the idea of multiple
| | 00:49 | applications at the same time.
| | 00:51 | We're talking about multithreading,
the ability for our application to do
| | 00:55 | multiple things at the same time.
| | 00:57 | You still might be thinking,
well, why is this important?
| | 01:00 | Well, the issue is that it's
all about queues on our machine.
| | 01:06 | We have a main queue, and that
performs tasks one after the other, in order,
| | 01:12 | whether it's updating the UI,
performing calculations, connecting to a
| | 01:16 | database, whatsoever.
| | 01:17 | We can, with GCD, easily create a
custom queue, take some of these tasks off,
| | 01:23 | and make them run on their own.
| | 01:26 | The operating system will take care of
all of this, making sure it happens
| | 01:31 | responsively, and making
sure it happens smoothly.
| | 01:35 | One of the key situations here is
the ability to make sure that our user
| | 01:38 | interface stays responsive, even
while things go on in the background.
| | 01:43 | So, how would we use and work
with Grand Central Dispatch in our
| | 01:46 | iPhone applications?
| | 01:47 | Well, essentially, there are two steps to it.
| | 01:49 | Step one, you create a new queue, you
give it a name, you give it whatever is
| | 01:54 | meaningful to you, that this queue would
be for networking, or this queue is for
| | 01:58 | long running calculations.
| | 02:00 | Then you simply create blocks and
drop them into the queue. That's step 2.
| | 02:04 | Step 3 is nothing. That's about it.
| | 02:07 | Okay, so, there is a little bit more advanced
parts of this, if you want to get deep into it.
| | 02:13 | But for most people, most of the
time, the whole process of using Grand
| | 02:17 | Central Dispatch is:
| | 02:18 | make a new queue in your application,
and add blocks to the queue - let iOS take
| | 02:23 | care of everything else.
| | 02:26 | If you are someone who has programmed
multithreaded code before, you'll know
| | 02:29 | it's a fairly unpleasant
thing to do. But I'm serious
| | 02:32 | when I say that Grand Central Dispatch
doesn't just make multithreaded code easier;
| | 02:37 | it makes it easy.
| | 02:39 | For the most part, we're going to be
interested in two commands that we're going to execute.
| | 02:44 | One is to create a new queue.
| | 02:46 | There is a dispatch_queue_create.
| | 02:50 | The way that we use it is essentially to
create a variable, in this case called
| | 02:54 | myQueue, and I give it a label.
| | 02:56 | That's pretty much it.
| | 02:58 | I've said here myQueueName.
| | 02:59 | This could be a descriptive label
for what this queue is being used for.
| | 03:03 | The reason we give it a label is
if we want to see it in debugging
| | 03:06 | information later on.
| | 03:08 | When I show an example, I'll show
that the typical label that we give it
| | 03:12 | is actually Reverse-DNS notation,
but you can put any text in here that's
| | 03:16 | meaningful for you.
| | 03:18 | This is how we create a queue, so I've
now got a queue called myQueue, and then
| | 03:22 | I'm going to use the other most
important command in Grand Central Dispatch,
| | 03:27 | which is dispatch_async, and
this simply takes two parameters:
| | 03:33 | the name of the queue that we're
dispatching to, and then a block.
| | 03:37 | I can see the caret here, with the curly
braces that contain everything that we
| | 03:41 | want to do asynchronously, which in
this case is just passing in a command to
| | 03:46 | run a long running operation method.
| | 03:49 | So, we do have to use blocks to be
able to use Grand Central Dispatch, but
| | 03:53 | between those two, and learning a
little bit about them, you might be amazed
| | 03:58 | at the amount of stuff that you can do with
creating your own multithreaded applications.
| | 04:02 | Well, I've talked for a
while about how easy this is.
| | 04:05 | I've shown you a couple of the
keywords. But to prove it actually
| | 04:08 | is significantly easy.
| | 04:09 | In the next module, we're going to go
through and show exactly how to convert an
| | 04:14 | application from single-
threaded into multithreaded.
| | Collapse this transcript |
| Multithreading with blocks and Grand Central Dispatch| 00:00 | In this example, I'm going to use Grand
Central Dispatch and blocks to take an
| | 00:05 | application that's currently not
very responsive with the UI and make it
| | 00:09 | multithreaded, with just a few lines of code.
| | 00:11 | So right now I have this application
| | 00:13 | here, I've got a date picker on there,
I've got this button that says complex
| | 00:16 | operation, and if I hit the button, the
actual UI is going to lock up for a few
| | 00:21 | seconds while it goes and does a big
complex operation in the background.
| | 00:25 | Trying that one again, I can actually
see that as I hit that button, any UI
| | 00:29 | element locks up, the button itself is
not responsive anymore, until after a few
| | 00:34 | seconds it comes back.
| | 00:36 | This is not good behavior.
| | 00:38 | Your users aren't going to like it.
| | 00:39 | It doesn't feel professional.
| | 00:40 | It doesn't feel useful.
| | 00:42 | So let's deal with that.
| | 00:44 | First, I'll explain what's actually happening.
| | 00:46 | As you might expect, this
isn't a super complex application.
| | 00:50 | I have a button here, I have a
label, and I have a date picker.
| | 00:53 | The date picker isn't even
hooked up to any background code.
| | 00:57 | It's just to show that even the
simplest user interface element will become
| | 01:01 | non-responsive if your main
thread is actually blocking.
| | 01:05 | So back in the code,
no huge surprises here,
| | 01:09 | what's happening is that the button
is calling an IBAction method called
| | 01:13 | doSomething that itself
calls a longRunningOperation.
| | 01:17 | And longRunningOperation
is remarkably complex here.
| | 01:20 | It's going to call sleepForTimeInterval:
| | 01:23 | 5 which means it go to sleep for 5 seconds
and then change the text of the message label.
| | 01:29 | And that's all that I have going on here.
| | 01:31 | There is nothing else in the
ViewController header file, apart from those
| | 01:34 | declarations, and that's all
that's in the implementation file.
| | 01:38 | The only extra thing I have is an
import to the stdlib.h, so I can use
| | 01:45 | arc4random, random number
generator, but that's it.
| | 01:49 | We don't have anything in
here to do with threading at all.
| | 01:52 | So I'm going to use Grand Central
Dispatch, and I don't have to link to any new
| | 01:56 | framework, but I do have to do an
import statement here which is #import
| | 02:01 | <dispatch/dispatch.h>.
| | 02:06 | And then I'm ready to go.
| | 02:07 | Because what we want to do with Grand
Central Dispatch is use its queues, I'm
| | 02:11 | going to just define one
queue that I want to use here.
| | 02:14 | Obviously, we have the main queue we
can always work on, but I'm going to have
| | 02:18 | one custom queue for myself here,
which would be a dispatch_queue_t, called myQueue.
| | 02:27 | I'm just defining here, so it's global
across multiple methods and whatever
| | 02:31 | functions I might have.
| | 02:32 | And the question is, where do I create it?
| | 02:34 | Well I could create it in a whole
bunch of different places, but just for our
| | 02:38 | purposes, let's make it and viewDidLoad.
| | 02:41 | I'm going to say myQueue =,
and I call dispatch_queue_create.
| | 02:49 | And this takes two arguments, a label
and an attributes list, which right now
| | 02:54 | the attributes list isn't being used.
| | 02:56 | They're both actually optional, but the
recommendation is you give it a label,
| | 03:00 | and we don't have to use the at sign here;
| | 03:02 | it's just a string of characters.
| | 03:04 | That could be anything that you want.
| | 03:06 | Give it some meaningful name.
| | 03:08 | It could be something like networkqueue.
| | 03:11 | In fact, the recommendation is that the
format you use is reversed DNS, based on
| | 03:16 | the name of your company,
so I could do com.lynda.
| | 03:21 | and then something meaningful for
my application, which here is my Grand
| | 03:25 | Central Dispatch Test.
| | 03:27 | And if I had multiple queues, I might
even say I had a networkqueue, and I had a
| | 03:31 | calculationqueue. These are just useful names.
| | 03:34 | Why do we want these labels?
| | 03:36 | Well, because if we were debugging
later on, and we've issues with the queues,
| | 03:40 | this is going to show up in instruments,
it'll show up as debugging information,
| | 03:44 | and let us know where an
issue might have occurred.
| | 03:47 | And after that, I can just
say NULL. I don't need that.
| | 03:50 | So this will actually create the queue object.
| | 03:53 | They're pretty light weight, so I
don't really worry about having this guy
| | 03:56 | created for the lifetime of the application.
| | 03:59 | So the question is, what
am I going to do with it?
| | 04:02 | Well I haven't anything yet, but here is
the call that's giving us the issue right now.
| | 04:07 | Where I called longRunningOperation, it's
hanging for 5 seconds before it manages
| | 04:13 | to get some data back and
then actually update the message.
| | 04:17 | So here is the way I'm going to make it work.
| | 04:19 | To actually call this on
another queue, all that we do is type in
| | 04:26 | "dispatch_async", and it's
going to ask for two things:
| | 04:31 | What queue am I sending this to,
and what am I sending to the queue,
| | 04:34 | which is, what queue is it,
| | 04:35 | the first parameter. And what's the block?
| | 04:37 | We have to use blocks to do it.
| | 04:40 | So my first parameter is just myQueue,
and then the second one is just a block.
| | 04:45 | I could have created it separately
before as a block variable, but more
| | 04:49 | typically, I'm just going to use the
caret, and then I'm going to use my open
| | 04:54 | braces to define the actual block itself.
| | 04:57 | Bear in mind, this is just the
same, so I end the statement.
| | 05:00 | And all I'm going to do is copy the
code that I want to execute and paste
| | 05:05 | it inside the block. That's it.
| | 05:07 | We will now take this call to
longRunningOperation, and we'll wrap it in the block.
| | 05:12 | We'll dump it in the other queue, and
Grand Central Dispatch will take care of
| | 05:16 | executing it on the other queue.
| | 05:18 | So let's go ahead and build this.
| | 05:20 | Build has succeeded.
| | 05:21 | I'm going to go and run it now.
| | 05:24 | So I can play around with this UI.
| | 05:26 | I'm going to click the button, and
the UI is actually still responsive.
| | 05:29 | I haven't got a result back yet.
| | 05:32 | And it's taking a few seconds.
| | 05:33 | I thought 5 seconds would do it.
| | 05:35 | I don't seem to have a result yet.
| | 05:37 | I do eventually get a result, but
this is not quite what I was expecting.
| | 05:41 | What's the problem here?
| | 05:42 | Well, here's the issue.
| | 05:44 | We've solved one part of it, which is
that our UI is still actually responsive.
| | 05:49 | We're not blocking the entire application.
| | 05:51 | That's a good thing, but it's
not quite as quick as we'd hoped.
| | 05:54 | The issue is actually back here.
| | 05:57 | We're calling longRunningOperation on
that other queue, which is fine, but I've
| | 06:03 | got to look it what it's doing.
| | 06:04 | We're sleeping for five seconds, and then
this second line here is updating that UI label.
| | 06:11 | Well here's the thing.
| | 06:12 | You're not meant to update user
interface elements from any thread other
| | 06:17 | than the main thread.
| | 06:19 | And it's not that you can't,
it's not that it's going to crash;
| | 06:21 | it's just not reliable.
| | 06:23 | You don't really have an awful lot of
control about how quickly things are
| | 06:26 | updating to the main screen.
| | 06:28 | So this line needs to be executed back
on the main thread, and we might think,
| | 06:34 | okay well, how do we do this?
| | 06:36 | So we're going to then call
longRunningOperation and immediately have
| | 06:40 | another one being called?
| | 06:41 | Well here is the great thing.
| | 06:43 | I can dispatch_async within this call to
actually put this one back on the main thread.
| | 06:49 | So I'm just going to do another dispatch_async.
| | 06:51 | Now, I'm not going to send this to
myQueue because that's the new one, the
| | 06:58 | custom queue that I created.
| | 07:00 | I can actually get hold of the main
queue by just calling a function called
| | 07:06 | dispatch_get_main_queue().
| | 07:10 | That takes parentheses here.
| | 07:13 | And then it's asking, well, what's
the block that we want to pass in?
| | 07:16 | Well again, we'll do the same format.
| | 07:18 | The block is just using the caret and
then the contents of the code that we're
| | 07:21 | going to execute will be content in
curly braces before the line ends.
| | 07:26 | To make this one a bit more readable,
let me split it up onto multiple lines.
| | 07:32 | Grab the line that updates the
user interface, and paste it in there.
| | 07:39 | Get a little ugly in terms of code
right now, but it should do the trick.
| | 07:42 | I'm going to then build that,
the build has succeeded,
| | 07:46 | and go ahead and Build and Run.
| | 07:50 | And what should happen now is before we
click the button we have a responsive UI.
| | 07:54 | I'm going to click the button, the UI
is still responsive, but within a few
| | 07:58 | seconds we should actually get our result
back, and it's actually quicker than last time.
| | 08:02 | That was about five
seconds, which is about right.
| | 08:04 | Might not be exact.
| | 08:05 | We're still dealing with
multithreading applications.
| | 08:09 | But this is how we can use Grand
Central Dispatch and blocks to very simply,
| | 08:14 | very easily turn our application from
single-threaded into multithreaded without
| | 08:20 | even saying the word
'thread' anywhere in our code.
| | 08:23 | Very simple stuff, very powerful stuff to do.
| | Collapse this transcript |
|
|
5. Using iAd to Integrate AdvertisingIntroduction to iAd| 00:00 | One of the completely new frameworks in
iOS 4 is the iAd framework, where you can
| | 00:05 | build an advertising into your
applications by allocating a section of your
| | 00:10 | application screen for those ads.
| | 00:12 | Now, you could, of course, do this
yourself. If you had a willing sponsor and if
| | 00:16 | this ad was just an image that linked
somewhere, you could create a button, put
| | 00:20 | an image behind it, and
respond to a touchdown event.
| | 00:23 | But the idea here is that you're really
allocating this area for Apple, and you
| | 00:28 | have no idea what's going to go here.
| | 00:31 | Apple is going to sell the ads and
deliver them into your application.
| | 00:35 | You'll get paid based on the amount
of times user see and touch the ads.
| | 00:39 | So you might do this as say a revenue
stream for a free application, but it does
| | 00:44 | mean you need to carve out some space in
your user interface to serve the ads, or
| | 00:48 | at least in some parts of your UI.
| | 00:50 | You don't have to do this in all of it.
| | 00:53 | You most typically have a banner
view that shows one of these ads.
| | 00:57 | The user can, if they want, tap the ad.
| | 00:59 | If they do, the typical model is
they'll go fullscreen into an ad with
| | 01:04 | additional content, while your app
pauses and waits patiently until they're done,
| | 01:09 | and go back to the application.
| | 01:11 | Now, the idea is that you'll get
60% of the ad revenue involved.
| | 01:15 | Now, as money is involved here, step
one of this entire process has nothing to
| | 01:20 | do with programming; instead, you must
log on to your iTunes Connect account and
| | 01:24 | provide all kinds of financial and
banking information if you haven't already
| | 01:29 | set that up by publishing a
commercial app on the App Store.
| | 01:32 | Check the developer portal for
the current step-by-step on this.
| | 01:36 | Step two is allocate an area of
space for the banner in whatever view
| | 01:42 | controller you want ads in.
| | 01:43 | We'll use the new
AdBannerView class to handle the banner.
| | 01:47 | You can do this programmatically, or
there is one you can drag and drop
| | 01:50 | in interface builder.
| | 01:52 | Now, as you might expect,
this is an Apple control.
| | 01:55 | It's capable of delegation.
| | 01:57 | There is got to be some communication going
on between the BannerView and your application.
| | 02:02 | Now, while most of the process of
loading ads is taken care of on the Apple
| | 02:07 | side, step three is building in
code to deal with ad-related events.
| | 02:12 | There aren't too many delegate methods
from this, but the three most common ones are:
| | 02:17 | There is a bannerViewDidLoad method.
| | 02:19 | So if you're creating the banner
view programmatically, you'll wait until
| | 02:23 | successful load message
before you make it visible.
| | 02:26 | There is a didFailToReceiveAdWithError
method, say a banner ad tries to load
| | 02:32 | something, and you have Airplane mode on.
| | 02:35 | Now, when the user taps a banner
view, you'll get the wonderfully titled
| | 02:39 | bannerViewActionShouldBegin:
| | 02:42 | WillLeaveApplication, which is a
Boolean value in the second argument here.
| | 02:46 | When this is called, the user has tapped
the ad, and we're about to do whatever
| | 02:52 | the banner ad wants to do.
| | 02:53 | That might just be just show
content, but then might be go to
| | 02:57 | another application.
| | 02:59 | If this willLeaveApplication
parameter is yes, it's because another app is
| | 03:04 | used to serve this ad.
| | 03:06 | For example, the user touches the ad,
and it takes them to a web page in Safari.
| | 03:11 | In which case, your app is going to
move to the background right after this
| | 03:15 | method is called, and it's time to pause
any activity and perhaps save state in the app.
| | 03:20 | Now, you do get the opportunity to
reject that banner ad behavior by
| | 03:24 | returning no from this method, but
if you're not going to do what the ad
| | 03:28 | wants to do, you won't get your 60%.
| | 03:30 | Now, the ads that will be placed here can be
customized to be relevant to your application.
| | 03:36 | Again, check iTunes Connect for the
latest information on what advertising
| | 03:40 | services are available at any given time.
| | 03:42 | Now, Apple's iAd network can provide
test ads while you're developing and
| | 03:48 | testing your application.
| | 03:49 | It even provides errors once in
a while to test your error handling.
| | 03:53 | And in the next few movies, we'll see
exactly how to accomplish all of this.
| | Collapse this transcript |
| Using iAd banner components| 00:00 | If you want to see the simplest way of
getting ads in your application, there's
| | 00:04 | actually an AdBannerView control in
the Library and Interface Builder, and
| | 00:08 | there is really not much to it.
| | 00:10 | I'm going to create just a simple project here.
| | 00:13 | I'll call it AdTest.
| | 00:14 | It's a view-based application, and once
Xcode is open, I'm just going to open up
| | 00:19 | ViewController in interface builder.
| | 00:21 | I'll go to my Library, and I'll scan
it and find the AdBannerView, and I can
| | 00:30 | just drag that onto my view, either at the
top or the bottom, or wherever it make sense.
| | 00:35 | This can be marginally useful when I'm
laying out a demo interface, because we
| | 00:40 | can use it to remind ourselves by
looking at the attributes of it, of the height
| | 00:45 | and width of the Ad BannerView control.
| | 00:47 | By default, it's going to be 320 x 50
when it's in Portrait mode, and it's the
| | 00:53 | most common mode, obviously.
| | 00:54 | Now, technically, if you've got the
iPhone 4 Retina Display, it's using more
| | 00:58 | pixels, but it scales.
| | 01:00 | We don't have to care.
| | 01:01 | It can also be set to 480 x 32, but
that's if your View itself is actually in the
| | 01:08 | landscape orientation, or most commonly
what you're going to do is just use both
| | 01:16 | selecting the AdBannerView and
allow it be both 320 x 50 and 480 x 32.
| | 01:20 | I'm just going to save
that and go back into Xcode.
| | 01:26 | One of the things I do need to do is
make sure that I link to the iAd framework;
| | 01:31 | otherwise, it's going to
be a real short trip here.
| | 01:33 | So I'm going to right-click on
Frameworks, click Add, and find
| | 01:37 | Existing Frameworks.
| | 01:40 | Find iAd, double-click it. There we go.
| | 01:44 | Save, Build and Run.
| | 01:46 | Now, when the AdBannerView is
created, it attempts to connect to Apple's
| | 01:52 | servers and load and add.
| | 01:55 | Let me just open that up a
bit bigger, so we can see it.
| | 01:59 | We're seeing here a Test Ad from Apple.
| | 02:02 | While you're in
development, this is all you'll see.
| | 02:05 | It's only when you start to configure
your app for publication to the app store
| | 02:08 | by using iTunes Connect, you'll
get a chance to see some real ads.
| | 02:12 | So a Test Ad appears. We can click it.
| | 02:16 | Our own app is covered with the ad.
| | 02:18 | We can view the advertisement,
or we can take a look at it.
| | 02:22 | We can close it back down, and Apple
takes care of animating it away and we return
| | 02:27 | to our main application.
| | 02:29 | Now just closing this down, what we can
see when we're running this is there's
| | 02:34 | a bit of a lack when this app is first
loaded before the ad actually appears
| | 02:39 | in the ADBannerView.
| | 02:41 | This is something we just have to live
with if this is how we're dealing with
| | 02:44 | this control, by just dropping it onto
our UI. But here is the deal: I really only
| | 02:50 | find it useful to work with the
AdBannerView this way when I'm creating a rough
| | 02:56 | demo of a user interface, and I want to
see how the AdBannerView plays with the
| | 03:01 | other controls that I
might be positioning on the UI.
| | 03:04 | When I build the real app, I do prefer to
programmatically work with the Banner View.
| | 03:09 | It is pretty easy to do, and I can do
things like animate this AdBannerView
| | 03:13 | onscreen only after an ad is loaded.
| | 03:16 | In fact, one of the downsides of
just dropping the AdBannerView into your
| | 03:20 | application and using it that way is
if anything does interfere with an ad
| | 03:26 | loading, such as the network having
problems, Apple really don't like this.
| | 03:31 | Some people have actually reported
there apps being rejected, if they have the
| | 03:36 | ad placeholder with just a blank space there.
| | 03:39 | So we're going to see how to work with
this programmatically and take care of
| | 03:42 | that event, and others, next.
| | Collapse this transcript |
| Responding to iAd lifecycle events| 00:00 | So, let's see how to work
with banner ads in code.
| | 00:03 | I'm going to create a
new view-based application.
| | 00:06 | I'll call this AdProject.
| | 00:11 | Maximize it to give it some space.
| | 00:13 | The first thing I'm going to do is make
sure that we are linking to the iAd.framework.
| | 00:17 | There is not much we can do without it.
| | 00:20 | Now, all of the code I'm going to write is
actually going to be in my ViewController.
| | 00:29 | I'm going to create an instance of the
ADBannerView, and start working with its
| | 00:33 | different Delegate methods.
| | 00:35 | Now, because I'm going to work with
its Delegate methods, I better go and say
| | 00:39 | that I'm going to do that.
| | 00:40 | So, first in my header file here, I'm
going to import the iAd header file.
| | 00:46 | Then I'm going to say, yes, I will
implement the Delegate protocol for the
| | 00:50 | ADBannerViewDelegate.
| | 00:52 | Once that is done, I'll save it, and
jump over to the implementation file.
| | 00:56 | The first thing I really need to do is
actually create the ADBannerView in the first place.
| | 01:00 | So, I'm going to come down to our
viewDidLoad here, uncomment that, and actually
| | 01:06 | put in the code to create it.
| | 01:10 | I'm going to create the
ADBannerView right at the bottom of the screen.
| | 01:14 | This is the line I'm going to use to do it.
| | 01:16 | So, by allocating and initializing it
with a position, where it's 460 pixels
| | 01:22 | off the top - now, bearing in mind that we
already have the Status Bar, which is 20 pixels -
| | 01:26 | that adds up and pushes it all the way down.
| | 01:29 | so the first line of pixels is
just below the bottom of the screen.
| | 01:33 | So, we can't see it.
| | 01:34 | I'm going to have to write some code
to make it animate up after an ad has
| | 01:39 | actually loaded into it.
| | 01:41 | Then I'm going to set its
ContentSizeIdentifier to 320 x 50.
| | 01:45 | This is the classic portrait orientation.
| | 01:48 | That's all I'm interested in supporting here.
| | 01:50 | I'm then going to say that this
banner is Delegate is self, meaning
| | 01:54 | this ViewController.
| | 01:55 | So, here is where I'm going to
write the code for the Delegate methods.
| | 01:58 | Then I'm going to just
add it to the current view.
| | 02:01 | That should take care of actually
creating a banner, and the actual banner ad
| | 02:06 | itself will take care of loading
its own ads from Apple's servers.
| | 02:10 | The issue is, of course, it's invisible.
| | 02:12 | So, what I need to do is
actually add some code to show it.
| | 02:16 | The way we're going to do it is this.
| | 02:18 | We're going to implement the
bannerViewDidLoadAd method.
| | 02:23 | I'm going to create an animation block here.
| | 02:25 | What this is going to do is just
change the position of it by -50 pixels.
| | 02:30 | That's going to move it up.
| | 02:31 | We've got a beginAnimations call,
and a commitAnimations call.
| | 02:35 | That's all we're doing in
the middle. Close that.
| | 02:38 | I'm going to save it.
| | 02:39 | Then I'm going to run this and see what happens.
| | 02:43 | Our app loads into the simulator, and the ad
loads and then animates up from the bottom.
| | 02:49 | Looks pretty good!
| | 02:51 | I'll admit that the rest of my
application isn't looking that exciting, but it
| | 02:54 | seems that the test ads
are being loaded correctly.
| | 02:57 | I can just watch that all day
long and think how fantastic it is.
| | 03:01 | The problem is we're shortly
going to run into an issue.
| | 03:04 | There is the issue.
| | 03:06 | The ads get loaded every 30 seconds or so.
| | 03:09 | It tries to load another ad.
| | 03:11 | Even though you didn't see a visual
change with the ad, another ad was loaded,
| | 03:16 | and the bannerViewDidLoadAd Delegate
method was called, and it shifted up again
| | 03:22 | by another 50 pixels.
| | 03:23 | It could keep on doing this.
| | 03:25 | This is not what I want.
| | 03:27 | So, I'm going to have to add a line or
two of code to make sure that it only
| | 03:31 | gets animated if it was
off the bottom of the screen.
| | 03:34 | So, going back to the code, what I'm
going to just do is, up towards the top here,
| | 03:39 | I'm going to create a Boolean
variable to hold that value.
| | 03:43 | Let's call it bannerVisible = NO.
| | 03:48 | Then down in my bannerViewDidLoadAd
I'm going to ask, if it's not visible,
| | 03:55 | we'll execute this block.
| | 03:59 | I'll then set bannerVisible
to YES, and close that block.
| | 04:06 | So, that should take care of that.
| | 04:08 | Now, the problem is this:
| | 04:10 | this leads us into the idea that
because ads are attempted to load multiple
| | 04:15 | times, every 30 seconds or so, it could
also fail multiple times, and maybe the
| | 04:19 | first one loaded, but the second time it
didn't, or the third time it didn't, or
| | 04:23 | the fourth time it didn't.
| | 04:25 | So, we could have it animate smoothly
out from the bottom of the screen, and
| | 04:28 | then 30 seconds later, it could
be empty and look all inelegant.
| | 04:33 | Well, the way that we take care of
that is by adding another method here.
| | 04:36 | I'm going to do the opposite one.
| | 04:38 | This is the bannerView:
| | 04:40 | didFailToRecieveAdWithError.
| | 04:40 | I don't really care about the error.
| | 04:44 | I just care about this method.
| | 04:46 | If that happens, I'm going to
ask, is the banner ad visible?
| | 04:50 | If it is, set up another animation block,
move it back down 50 pixels, and then
| | 04:56 | set it back to being invisible.
| | 04:58 | I'm not going to run this and test this.
| | 05:01 | You could test this yourself just by
opening up the application, allowing the
| | 05:05 | first one to load, and then say,
turning off your Internet access.
| | 05:09 | Then what you'll see is
it'll try once to load an ad.
| | 05:12 | If that fails, it'll keep the old ad around.
| | 05:15 | Then it'll try again.
| | 05:16 | If it can't load another one,
it'll animate right back off.
| | 05:20 | So, let's run that application. The ad loads.
| | 05:24 | We've got it. We could tap it.
| | 05:27 | We get the actual advertisement
going fullscreen over the top of our app.
| | 05:32 | Then I could close it and go back down.
| | 05:33 | Now, here is the thing:
| | 05:35 | What if I was doing
something really important in my app -
| | 05:39 | okay, I've got a gray screen
right now, but it could happen -
| | 05:42 | What if I was doing something really
important at the time the user tapped here?
| | 05:45 | Well, is there any way of
finding out that that user has tapped?
| | 05:48 | Well, of course, yes, there is.
| | 05:50 | So, going back to the code, what I'm
going to do is implement another one of the
| | 05:56 | Delegate methods, which is
bannerViewActionShouldBegin:willLeaveApplication.
| | 06:03 | As soon as the user taps the ad, but
before the actual main advert is showing,
| | 06:08 | we'll get this called.
| | 06:09 | We get the opportunity to
do anything really important.
| | 06:13 | We also get the opportunity to say no, I
don't want the ad to be shown, although
| | 06:17 | you want to be careful with that.
| | 06:19 | So in this, I'm just going to do an NSLog here.
| | 06:22 | Pause anything necessary.
| | 06:23 | Maybe I've got an animation
going on that I want to pause.
| | 06:26 | But after that, what I'm just
going to do is say return YES.
| | 06:30 | This is basically saying, yeah, go ahead and
show the actual advertisement. I could return NO.
| | 06:37 | If I return NO, the ad will not be shown.
| | 06:39 | I'll just stay in the app.
| | 06:41 | But of course, you won't get any money, and
| | 06:43 | if you're doing your ads for a revenue
stream, this is not something that you
| | 06:46 | want to return NO on very often.
| | 06:49 | But you might do it if your app is
doing something super, super important.
| | 06:53 | Now, the second parameter here of
willLeaveApplication is a Boolean value.
| | 06:59 | It's basically saying, when the user
taps the ad, do we stay in the app and just
| | 07:03 | show that little overlay ad, or do
we actually go to another application,
| | 07:08 | meaning do we go to
Safari and open up a web page?
| | 07:12 | If the willLeave parameter that gets
passed in is YES, then as soon as this
| | 07:18 | method returns, our app is going to
move to the background, using the classic
| | 07:22 | multitasking willMoveToBackground method.
| | 07:25 | It's going to just end up in the suspended mode.
| | 07:29 | If the willLeaveApplication parameter
is NO, what that means, instead, is we're
| | 07:34 | just going to overlay our
application with the temporary ad.
| | 07:38 | The user is going to read it.
| | 07:39 | They're going to click the Close
button, and go back to our application.
| | 07:42 | Our application has not moved to
the background; it's still there.
| | 07:46 | In fact, if that will be the most common
way of doing it, what I'm then going to
| | 07:49 | do is do the flip side of this.
| | 07:52 | What happens when we come back to our app?
| | 07:54 | Well, we've got another method
here called bannerViewActionDidFinish.
| | 08:00 | This would allow us to do any
restarting of any animations or
| | 08:04 | anything important.
| | 08:05 | So, I'll just log a message here,
"We now resume our normal operations."
| | 08:09 | That should do the trick.
| | 08:10 | Let's save and compile this, build succeeded.
| | 08:13 | I'm going to run this.
| | 08:15 | The app opens. The ad loads.
| | 08:18 | Let me open up the little GDB here and just
clear this Log off, so we can see our messages.
| | 08:25 | I tap the ad.
| | 08:26 | We get the message, "Pause anything necessary."
| | 08:28 | I also see this message a lot.
| | 08:30 | It doesn't seem to interfere with
the actual generation of the ads.
| | 08:34 | I know many people are actually
getting this message about shouldAutoRotate,
| | 08:38 | but it doesn't seem to be
anything that we're doing.
| | 08:40 | I'm going to then close this down.
| | 08:42 | I would expect to see the message,
"Normal operations are resuming." There we go!
| | 08:47 | Back to the application.
| | 08:48 | So, we're not moving to the background; our
application stays alive while we're doing this.
| | 08:55 | This is the core of
working with ads on the iPhone.
| | 08:58 | Once you've got your app signed to a
distribution, you can actually start seeing
| | 09:02 | the real ads, and start making some money.
| | 09:04 | Up until that point, we'll just be
seeing Apple's test advertisements.
| | Collapse this transcript |
|
|
6. Working with Audio and VideoIntroduction to the Assets Library framework| 00:00 | The Assets Library is a new framework.
| | 00:02 | It's a way to interact with the
photos and videos stored on the device.
| | 00:07 | Now, an Asset Library might first sound
like a generic way to manage all media
| | 00:12 | assets, but it's not.
| | 00:14 | This is not about audio, and it's not
about vector image in graphic files.
| | 00:19 | It's just the things you have in the
Photos application: photos and video,
| | 00:25 | either the photos, and video taken by
the iPhone, or albums copied or synced to
| | 00:30 | the device using iTunes.
| | 00:32 | If you, or your users manage, photos
with iPhoto on the Mac and sync those
| | 00:37 | back to the device, you can get to those
albums, things like events, faces and places.
| | 00:44 | So the same way that the Event Kit
framework lets you get at things that you'd
| | 00:48 | create in the calendar, the Assets
Library can be viewed as a way to get to the
| | 00:52 | things you'd see in the Photos application.
| | 00:55 | It can also be used to save
from your app to that library.
| | 00:59 | Now because there's a potential risk
in allowing an application access to the
| | 01:04 | location information stored in your
personal photos, your users will have the
| | 01:09 | ability to deny your app
access to see this data.
| | 01:12 | As with Event Kit, the Assets Library
is not something every application will
| | 01:17 | need, but it's a good example of the way
Apple are opening up device a bit more.
| | 01:22 | So you have the ability to really start
to integrate your apps into the calendar,
| | 01:26 | into the photos application.
| | 01:28 | Even if you're not immediately intending
to use these things, being aware of the
| | 01:32 | ways Apple are opening up their
programming APIs to do this is useful, so you
| | 01:38 | understand their programming model to do it.
| | 01:40 | For example, you can't use the Assets
Library without understanding the new
| | 01:44 | language construct called blocks.
| | 01:47 | So if we want to use it, what's the process?
| | 01:49 | Well first, we create an object to represent
the entire library of all photos and video.
| | 01:55 | This is called an AL, Assets Library.
| | 01:58 | This library contains groups.
| | 02:01 | The groups contain the individual assets, the
photos and the video, but what are these groups?
| | 02:08 | Well, the main one will always
be the classic saved photos album.
| | 02:12 | This is the one you see as camera
roll if it's on a device with a camera.
| | 02:16 | If you sync photos onto your device with
iTunes, you can also get to groups that
| | 02:20 | represent events and faces and places.
| | 02:23 | There's even a group that represents everything.
| | 02:26 | So some groups contain photos
that you would find in groups.
| | 02:30 | As on iPhoto on the Mac, if you have a
photo that's in an event, that same photo
| | 02:35 | could also be in a place group.
| | 02:38 | So do bear in mind that the same
photo can appear in multiple groups.
| | 02:42 | You can filter these groups so that
you're only looking at photos, or only
| | 02:45 | looking at video, but at the end,
accessing your assets is all about
| | 02:50 | grabbing the library, enumerating
through the groups and then enumerating
| | 02:54 | through the assets.
| | 02:56 | If you have the address of an
individual asset, you can get directly to it.
| | 03:00 | They all have an individual URL address,
but the only way to get that address is
| | 03:05 | either that your app created the asset
in the first place, or that you've already
| | 03:10 | enumerated through those
assets and picked an asset URL.
| | 03:14 | So we're going to see first, how to
access some core information about the Asset
| | 03:18 | Library and the things inside it, and
then when we later go into video, we'll
| | 03:22 | see how to save into it.
| | Collapse this transcript |
| Using the Assets Library in your application| 00:00 | I'm going to go ahead and create a
simple application that will go through the
| | 00:04 | groups and the assets in our Assets
Library, and get some information about them.
| | 00:09 | So I'll begin with a very
straightforward view-based application.
| | 00:12 | It's got one Get Asset Library Details
button that is hooked up right now to an
| | 00:18 | IBAction method called getStats,
and that's all that I have done.
| | 00:22 | Well, first thing that I need to do
is if we're going to use the Assets
| | 00:24 | Library framework, I better make sure my
project knows about that Assets Library framework.
| | 00:29 | So I will add it in here.
| | 00:32 | Then in my ViewController implementation file,
and that's where I'm going to put my code,
| | 00:36 | I will put an import statement
to the AssetsLibrary header file.
| | 00:42 | So what I'm going to do in this method,
when that button is pressed, I want to
| | 00:46 | count the number of groups
and count the number of assets.
| | 00:49 | So I'll create a couple of
integers, just to hold those values.
| | 00:53 | I may have to come back to those
integers in a moment. We'll see why.
| | 00:57 | Next is the most important step.
| | 00:59 | If I'm going to actually use the Assets
Library, I need to instantiate an object
| | 01:04 | to represent the library.
| | 01:05 | This is all we have to do, because
there is only one on the machine.
| | 01:09 | So I can just call alloc and
init on the ALAssetsLibrary object.
| | 01:13 | Next, I have to prepare it.
| | 01:15 | I have to say, okay, I'm going to put
together a call here, but I'm going to
| | 01:19 | tell it what groups I'm interested in.
| | 01:21 | There is actually an ALAssetsGroup type
enumeration that we can use here, which
| | 01:27 | could be GroupAll, GroupEvent,
GroupFaces, GroupLibrary, which is everything.
| | 01:32 | So I'm going to say
GroupAll, which includes all groups.
| | 01:36 | Do bear in mind that when you're
enumerating through the groups in the library,
| | 01:40 | you may get the same photo in multiple groups.
| | 01:44 | An example would be if you're sorting
your photos by faces and organizing them
| | 01:49 | that way. Well, the same photo that has
the face of Bob may also appear in the
| | 01:54 | group that represents an
event on a particular date.
| | 01:56 | Now if I say here that I'm going to
create something that represents all groups,
| | 02:02 | I'm expecting that the actual number
that I'll get is actually bigger than the
| | 02:06 | number of the actual photos.
| | 02:08 | Now next, what I have to do
is create a couple of blocks.
| | 02:12 | Now the first one that I'm going to
do is a block that will be used to
| | 02:17 | enumerate through the groups.
| | 02:18 | What's going to happen is every group
that exists, this block will be called.
| | 02:23 | The way that I do this is I'm creating an
| | 02:25 | ALAssetsLibraryGroupsEnumerationResultsBlock,
which I'm using the caret here for this block.
| | 02:32 | I'm going to create a little code here.
| | 02:36 | I'll explain that in just a second.
| | 02:39 | What this is actually going to do is say
I'll be called for every group that exists.
| | 02:44 | Well, I'll actually be
called once more than that.
| | 02:46 | So I'm going to first check is
the group object equal to something?
| | 02:49 | If there is a group, and it's not a null
object, I'm going to add one to my numberOfGroups.
| | 02:55 | I'm going to find the numberOfAssets
in the group and add that to my total.
| | 02:59 | I'm going to then write a
little NSLog message out here.
| | 03:03 | What I can do is grab hold of the
name of the group by using this format.
| | 03:08 | It's the valueForProperty and then passing
in this value that represents an enumeration.
| | 03:14 | We can grab the name of the group.
| | 03:16 | We can grab the type of the group.
| | 03:17 | We can grab the ID of the group.
| | 03:20 | Now this code won't be run
at this particular point.
| | 03:23 | It is, of course, a block that begins
with caret here and ends down here, but
| | 03:29 | we're going to be passing this into
a method call, and it will be called
| | 03:33 | repeatedly in just a moment.
| | 03:36 | Next, what I'm going to do is create
another block, which is the failure block.
| | 03:39 | What happens if when we're going to
through these groups, something goes wrong.
| | 03:43 | And this failure block just has
an error object passed into it.
| | 03:47 | I'll just do an NSLog at that point.
| | 03:49 | Nothing very spectacular here.
| | 03:52 | With those created, what I can do is
now call the enumerateGroupsWithTypes
| | 03:57 | method of the assetsLibrary
object, passing in the block list group
| | 04:02 | block, which is the first one, and the
failureBlock, if that's going to be necessary.
| | 04:07 | This will actually cause all
those blocks to be executed.
| | 04:10 | It'll actually call them on another
thread, which may give us a problem in a
| | 04:14 | moment, but let's imagine that
that happened with this line of code.
| | 04:18 | All those blocks got executed, and we went
through all the groups and counted up all the assets.
| | 04:23 | Well, next, what I'm going to do then is
just create a little message that will
| | 04:28 | be, there are so many groups with so
many total assets and just create a string.
| | 04:33 | After that, I'm going to create an
AlertView to just pop up the information.
| | 04:39 | The stats to the assets are this message.
| | 04:42 | I don't need a delegate object,
because I'm not doing anything other than
| | 04:45 | popping up a little alert view
with an Okay, cancelButton. That'll do it.
| | 04:51 | I'm going to show the alert.
| | 04:52 | I'm going to release the alert.
| | 04:54 | Then I'm going to release the only other
object that I actually manually created,
| | 04:59 | which was the assetsLibrary.
| | 05:01 | Now because this button could be clicked
a couple of times, I better go and just
| | 05:07 | undo numberOfAssets and
numberOfGroups and make sure they're back to 0.
| | 05:10 | So I'm going to save this.
| | 05:13 | Now, what will I do next?
| | 05:14 | Well, here is the deal.
| | 05:15 | I could show this in the simulator, but
I don't have any photos in the simulator.
| | 05:20 | Even though I can get a couple of
photos into the simulator, it's not really
| | 05:24 | going to be a very good example.
| | 05:26 | So I am actually going to use my device and
just show a screenshot or two as I'm running it.
| | 05:33 | So I'll go ahead and click Build
and Run to put this on my device.
| | 05:38 | I'm getting a build failed here.
| | 05:41 | What's the problem?
| | 05:42 | Yeah, here is the issue.
| | 05:43 | I'm getting a message here: Increment
of read-only variable numberOfGroups.
| | 05:47 | Now what's the deal?
| | 05:48 | While I did create these two integers
up here, and I'm trying to add to them in
| | 05:55 | the actual blocks, and that's not
allowed, because the way blocks work, these
| | 05:59 | are kind of considered constants at this point.
| | 06:01 | Now there are a couple of ways I could do it.
| | 06:03 | One is I could take both of these
integers and move them outside of this method,
| | 06:09 | make them global variables within this
class, and that would work. Or, another way
| | 06:13 | of doing it is I can say __block in
front of these to tell object to see, hey,
| | 06:21 | these variables I want to be
able to use inside a block.
| | 06:24 | So let's try and compile that one.
| | 06:26 | Now build has succeeded.
| | 06:28 | So I'm going to go ahead
and click Build and Run.
| | 06:33 | While I'm doing that,
I'll just open up my console.
| | 06:36 | It's popping up on my device.
| | 06:37 | You can imagine what the interface looks like.
| | 06:39 | There's just one button on it.
| | 06:40 | I'm going to touch the button.
| | 06:42 | Now, I can see here that in my Console,
it's gone through a bunch of these
| | 06:48 | groups, and it's giving me the name
of the group is, Camera Roll, Photo
| | 06:50 | Library, Santa Barbara.
| | 06:52 | I've got certain ones that are dates.
| | 06:54 | I've got other ones that
actually represent faces in there.
| | 06:58 | So it seems to be pulling the groups
out, but I've hit a problem, which is
| | 07:02 | looking on my device - I'll just
flick over to the Organizer, so I can
| | 07:05 | show you a screenshot - is the actual
alert message says There are 0 groups
| | 07:10 | with 0 total assets.
| | 07:12 | Now I think, oh, that doesn't seem right.
| | 07:15 | In fact, the message is telling me
it's gone through a whole bunch of groups.
| | 07:18 | So what's the problem?
| | 07:19 | Well, I might think, well, maybe it's
something to do with those variables again,
| | 07:23 | but we're actually having an
issue with blocks themselves.
| | 07:26 | I'll show you what I mean.
| | 07:27 | I'm actually on my device. I am
going to press that button again.
| | 07:32 | If I take another screenshot, it
actually seems to be working this time.
| | 07:36 | So what's the deal?
| | 07:37 | This looks about right.
| | 07:38 | There are 45 groups with 571 total assets.
| | 07:42 | Do remember that the same
assets may appear in multiple groups.
| | 07:46 | So I'm going to go back over to
my code here and just stop that.
| | 07:51 | The problem that we're having, and
the reason why I got the pop-up message
| | 07:56 | saying there are no groups and
there are no total assets is this: that we
| | 08:01 | created the two blocks that are being
used to enumerate through those groups and
| | 08:05 | if necessary put out an error message.
| | 08:09 | The issue is when I first called it,
when I first said go and loop through all
| | 08:14 | the groups, that was
kicked off on another thread.
| | 08:18 | The problem was we didn't come back from
it quick enough, and we jumped straight
| | 08:22 | ahead to creating the message
and outputting the AlertView.
| | 08:26 | But unfortunately, it did it so
quickly that the count was still 0.
| | 08:30 | Now, one of the reasons that happens is
because the first time your application
| | 08:35 | tries to access the Assets Library,
| | 08:37 | it's a little slow about it.
| | 08:38 | In fact, it goes and checks
if you're allowed to do that.
| | 08:42 | The first time you actually run this
application on a device, you'll find that
| | 08:45 | the device will ask, are you
allowed to look at location information?
| | 08:50 | So we're running into an issue where
it was a little slow the first time.
| | 08:52 | Now how do I deal with that?
| | 08:54 | Well, here is the interesting thing.
| | 08:57 | Up here in the block that's being
executed, this method will be called for every
| | 09:02 | group that exists, and it will also be
called one more time, where the parameter
| | 09:08 | that gets passed in is actually
nil, at the end of the enumeration.
| | 09:13 | What does that mean?
| | 09:14 | Well, it means that I can actually say,
well, if there is a group object, I'm
| | 09:18 | going to add to my totals here.
| | 09:20 | But if there isn't, then I'm done.
| | 09:23 | I'm actually at the end of the enumeration.
| | 09:25 | So there would be a suitable place
for me to put that error message.
| | 09:31 | So I'm creating an else block here.
| | 09:32 | I'm going to come down, copy the code
that I had to create the UIAlertView to
| | 09:39 | both create it, show it, and release it,
and come up and paste that into the
| | 09:45 | statements block here.
| | 09:47 | Save and build, build has succeeded.
| | 09:50 | I'm now going to open up my console,
just to clear it out, so we can take
| | 09:55 | a little look here.
| | 09:56 | Click Build and Run.
| | 09:57 | I'm going to tap the button once.
| | 10:04 | I see the groups popping up over there.
| | 10:07 | If I take a look at my screenshot, which
I'm going to capture again, it tells me
| | 10:12 | there are 45 groups with 571 total assets.
| | 10:15 | Of course, this is not the most
useful application in the world.
| | 10:21 | What we're really trying to go through
is the idea of how do we start to work
| | 10:26 | with the Assets Library?
| | 10:27 | The fact that because we don't the
individual URLs of each of the images that
| | 10:33 | are in each of the groups, we do have
to enumerate through them, we do have to
| | 10:37 | understand blocks, and we also have
to understand the behavior of blocks.
| | 10:41 | So this will be a good starting point.
| | 10:43 | Later on the course, we'll see how we
might do things such as create assets and
| | 10:48 | save video assets back into this library,
but this is how we begin to work with
| | 10:52 | the Assets Library framework in Xcode.
| | Collapse this transcript |
| Playing video in your application| 00:00 | When the iPad was released earlier in
2010, Apple did release a 3.2 version of
| | 00:06 | the SDK, which had some unique
classes in it for playing video on the iPad.
| | 00:11 | Now that we've got the 4.0 release
of the SDK, those also work on this
| | 00:15 | version of the iPhone too.
| | 00:16 | So, if you've done some iPad
programming with video, you may have already have
| | 00:19 | seen what I am about to talk about.
| | 00:21 | But if not, you may find this quite useful
for playing video in your own applications.
| | 00:26 | So, I have a very simple view-based
application, with one button on the view that
| | 00:31 | is hooked up to my buttonTabbed - (IBAction).
| | 00:33 | Now, what I am going to do first,
because I am going the media player framework,
| | 00:38 | well, I better go and add it.
| | 00:40 | So I am going to add the Existing
Framework that is MediaPlayer and although the
| | 00:46 | framework is media player, the actual
class that I am interested in here, and I
| | 00:50 | am going to create a variable of,
is the MPMoviePlayerViewController.
| | 00:58 | It's not recognizing it right now because
I really do need an import statement here.
| | 01:02 | I am just defining an instance of it,
which I will instantiate in just a minute
| | 01:09 | in my implementation file.
| | 01:12 | So, if I jump over into my
implementation, I have the skeleton of the
| | 01:17 | buttonTabbed method, which is
where I am going to put my code.
| | 01:20 | Well, first off, I better get
some video into my application.
| | 01:24 | Well, luckily, out here on the desktop, I have a
suitable video file, in this case water.m4v.
| | 01:29 | I am going to drop it in Resources.
| | 01:32 | You can either have the file included
in your application's bundle, if it's just
| | 01:36 | a small amount of video, or
you can provide a URL as well.
| | 01:40 | It really doesn't matter.
| | 01:41 | In this case, for convenience's sake I
want to actually have the physical file.
| | 01:45 | So, now it's under Resources
and copied into that location.
| | 01:51 | What I am going to do is basically
create a path to that location by just using
| | 01:58 | the NSBundle mainBundle method pathForResource.
| | 02:01 | So, this is me just grabbing a
path that I can then use in a moment.
| | 02:05 | After that, I am going to
allocate a new instance of that
| | 02:09 | MPMoviePlayerViewController that I
had declared in my interface, and I am
| | 02:14 | going to initialize it with the path that I
just created, creating a file URL from that path.
| | 02:21 | So, I have got two lines of code.
| | 02:22 | It might be a little annoying to look at,
but this will do the trick. Well, then what?
| | 02:28 | What I am going to do is
actually present the player.
| | 02:31 | Now, this is really the cool thing
about this new MoviePlayerViewController.
| | 02:35 | In earlier versions of the iPhone SDK,
we had a control to play video.
| | 02:39 | It was the MoviePlayerController.
| | 02:42 | This one is the MoviePlayerViewController.
| | 02:44 | One of the benefits that we have of
having it being a real view controller is we
| | 02:49 | have the ability to animate it to
present it mode-ly or in this case there is
| | 02:54 | actually a method called
presentMoviePlayerViewControllerAnimated.
| | 02:59 | What you'll get from using this method
on our view controller is to present it the
| | 03:04 | same way that an application like
YouTube application would do it.
| | 03:09 | With these three lines of code,
I am pretty much ready to go.
| | 03:12 | Now, I haven't actually released
the control yet, which is correct.
| | 03:17 | I wouldn't want to;
| | 03:18 | it might be still being used, but I am going to
go ahead and run it anyway and see what happens.
| | 03:22 | Now, I've got the small iPhone
simulator, so it's going to look a little odd.
| | 03:27 | But when I press this button, view should
pop up and immediately get the video playing.
| | 03:39 | If I rotate it, it would go into
fullscreen mode, and everything would work just fine.
| | 03:43 | Now, the issue here is what I don't
want to do is just call player release at
| | 03:49 | this point, because if I did that, we
are going to stop playing this video,
| | 03:54 | but we could get some odd behavior
happening, depending on if I'm moving around
| | 03:57 | resizing it using somewhere else.
| | 04:00 | So, the correct way to do it, which is a
little bit more complex, before we start
| | 04:06 | working with that, what I
am going to do before I call
| | 04:10 | presentMoviePlayerViewController is I
am going to write some code here that's
| | 04:14 | actually going to create a
Notification for the PlaybackDidFinish.
| | 04:20 | Now, this looks a little bit complex here.
What I am actually doing is using the
| | 04:24 | default notification center, adding
this class as an observer and saying, when
| | 04:30 | that movie player has finished playback,
I would like it to call me back and
| | 04:35 | find a method called movieFinishedPlaying.
| | 04:38 | This is the proper way to subscribe
to a notification that that movie has
| | 04:44 | actually finished in the movie player.
| | 04:46 | Well, I don't actually have a
movieFinishedPlaying method, so I am going to add that next.
| | 04:55 | Pulling myself off because I don't
need to be notified anymore, and then I
| | 05:00 | can release the player.
| | 05:02 | It's kind of an interesting thing that
it seems more complex getting rid of the
| | 05:06 | movie player than it is actually
loading it up with video and playing it.
| | 05:10 | But this is the suggested way that we
deal with this view controller object.
| | 05:15 | Now, while working with this new object is
a better object than the one we have before,
| | 05:19 | you might not be terribly impressed
if you have been working with video in
| | 05:23 | previous versions of the SDK.
| | 05:25 | Indeed, there are real benefits to
having this movie player be a true view
| | 05:29 | controller for something like the iPad,
where you have a much bigger screen.
| | 05:33 | You have all sorts of opportunities to
embed it inside other view controllers,
| | 05:38 | which aren't quite so blankly on the
iPhone, but this is the recommended object
| | 05:42 | that we should use for this kind of
playing, and this is the one that I wanted
| | 05:46 | to bring you up to date on.
| | Collapse this transcript |
| Recording video in your application| 00:00 | Continuing my tradition of one-button
applications, I am going to take this one
| | 00:05 | to show you how we can take some of the
classic controls that have been around
| | 00:09 | since early versions of the SDK, such as
the UIImagePickerController, tying that
| | 00:14 | together with some new classes and some
new frameworks, like the Assets Library
| | 00:18 | framework, in order to take video and
save it to our photos and videos library
| | 00:24 | directly from within our own application.
| | 00:27 | So once again, I have a pretty classic
view-based application with one button
| | 00:31 | that says Record on it.
| | 00:32 | That right now is just hooked up
to a ViewController with an IBAction
| | 00:37 | called recordVideo.
| | 00:39 | That IBAction is defined here,
but doesn't have anything in it yet.
| | 00:43 | The only other thing that I have
actually defined here is I am declaring a
| | 00:47 | UIImagePickerController, and making it a
property, and that's all I have done so
| | 00:52 | far to this project.
| | 00:54 | Now, there is a couple of things I
have to do to start things off here.
| | 00:58 | Now the ImagePickerController
has some powerful delegate methods.
| | 01:01 | So I do need to make sure that I am
implementing that Delegate protocol.
| | 01:06 | So I will say
UIImagePickerControllerDelegate. And it gets a little worried if
| | 01:11 | we also don't say it's a
NavigationControllerDelegate as well.
| | 01:15 | Now, I am going to be using a couple
of frameworks in the process of creating
| | 01:21 | the ImagePickerController and
saving to our own photos library.
| | 01:25 | So I am going to go to my Frameworks
location here and add Existing Frameworks.
| | 01:31 | I am first going to add AssetsLibrary,
which probably makes sense from the two
| | 01:35 | assets library movies that we have already done.
| | 01:38 | What I am also going to add might seem a
little puzzling, but it will make sense in a moment.
| | 01:42 | I am going to come down and find
the MobileCoreServices.framework.
| | 01:48 | The reason for this is that when we
are actually trying to restrict our
| | 01:52 | ImagePicker down to video, we need an
enumeration which is actually defined in here.
| | 01:58 | If it wasn't for that, I
wouldn't need that framework.
| | 02:01 | So that's my header defined.
| | 02:03 | I am going to switch over into my
implementation file here, and I am going to
| | 02:07 | do a couple of imports on
MobileCoreServices.framework that I just imported,
| | 02:15 | and on the AssetsLibrary.
| | 02:20 | So I have got my synthesize statement
in here, and I have my placeholder here
| | 02:25 | for recording video.
| | 02:27 | So what do I do next?
| | 02:28 | Well, here is where I am going to start
writing some of the code that we need to do.
| | 02:32 | So first, I am going to create the ImagePicker.
| | 02:34 | I will see if it exists.
| | 02:35 | If it doesn't, I will
instantiate it, a new instance of the
| | 02:39 | UIImagePickerController, and then just
set its Delegate to self, meaning this
| | 02:45 | current ViewController.
| | 02:47 | After that's done, what I am going to
do is ask, and this is good code here.
| | 02:50 | I am going to check if the camera is available.
| | 02:53 | I am going to ask here
the isSourceTypeAvailable:
| | 02:56 | UIImagePickerControllerSourceTypeCamera.
| | 02:59 | This is not new stuff.
| | 03:01 | This has been around for a while now,
since older versions of the iPhone SDK
| | 03:06 | where we could ask what's available.
| | 03:08 | So, for example, if you only had an
iPod touch, you might only have the photo
| | 03:12 | library available, so the
camera call would return no.
| | 03:17 | So if that is available, we are going
to continue on a little bit further, and
| | 03:21 | we are going to say if that's true,
find the available media types, and this is
| | 03:27 | the way that we do it.
| | 03:28 | I have broken this onto two lines to
make it more readable, but we are going to
| | 03:32 | get an array of media types by calling
the availableMediaTypesForSourceType.
| | 03:38 | What SourceType do we have?
| | 03:40 | We, by now, know that we have the camera.
| | 03:44 | What does this mean?
| | 03:45 | Well, it's basically going to come back
with an array of values that represent,
| | 03:49 | we can take pictures,
| | 03:50 | we can take video. And who knows, in
the future, whether we are taking 3D or
| | 03:55 | whatever we might be taking.
| | 03:56 | But after this, what I want to do
is ask if video is actually one of
| | 04:01 | the available types.
| | 04:02 | Was that array that was returned
contain the actual field that represents it
| | 04:08 | which is kUTTypeMovie.
| | 04:11 | This is actually the reason that I had
to add the link to MobileCoreServices
| | 04:16 | because this enumeration
is actually defined there.
| | 04:19 | So we are asking, what mediaTypes
are available? And after getting that
| | 04:24 | array back, because it could have
been say an iPhone 3G that has a camera
| | 04:28 | but can't take video.
| | 04:30 | So if that is just movies are available, I am
going to restrict the source type to camera.
| | 04:36 | I am then going to restrict the media type
to video, and this is the way that we do it.
| | 04:40 | The picker both has a sourceType.
| | 04:42 | It says we are going to use the camera,
and not only are we going to use the
| | 04:45 | camera, we are restricting the media
type to kUTTypeMovie, and the way that we
| | 04:50 | do it is by essentially creating an
array with basically only one entry in it
| | 04:55 | that says it's kUTTypeMovie.
| | 04:57 | We have now finished that one, and if
there was no video support, here is where
| | 05:03 | we could do a message if we saw fit.
| | 05:05 | Let me line those up a little bit and make
them hopefully a bit more obvious. There we go.
| | 05:12 | There is quite a lot of code being added here,
but hopefully it will begin to make sense.
| | 05:17 | So after that, we have
restricted it to the camera.
| | 05:21 | We have restricted it to only taking video.
| | 05:25 | Then we can present the picker.
| | 05:27 | Just a call to
presentModalViewController passing in the picker that we have
| | 05:31 | created, and now we have configured.
| | 05:34 | It will then show this to our user.
| | 05:36 | It will allow the user to start taking video.
| | 05:38 | It will give the user the opportunity
to either preview the video, to delete it
| | 05:42 | if they don't like it, or to retake it.
| | 05:44 | But we have to continue on and do
a little bit more code about what
| | 05:49 | happens after that.
| | 05:53 | One of the simplest delegate methods,
and the most important ones for the
| | 05:56 | ImagePicker, is the
ImagePickerControllerDidCancel.
| | 06:00 | So they saw the ImagePicker and decided
not to use it, and all we are going to
| | 06:04 | do here is dismiss that
ImagePicker, and then release it.
| | 06:09 | The most important one that we want is
well, what if they took some video, and
| | 06:13 | then said, choose, this is the one that I want.
| | 06:16 | Well, here is what happens.
| | 06:18 | We get the
ImagePickerControllerdidFinishPickingMediaWithInfo.
| | 06:23 | Bear in mind in some programs, I
might have to ask things like, is this a
| | 06:28 | picture that I am getting, is
this video that I am getting?
| | 06:30 | I know, because I have configured my
ImagePicker that if we go back into this
| | 06:34 | method, we have just got video.
| | 06:36 | So what I am going to do is
grab the URL of the recorded video.
| | 06:40 | It will be passed into me as
part of the info object here.
| | 06:44 | So I will do an info objectForKey, and the
key that I am interested in is the URL of it.
| | 06:50 | I am actually going to write that out as
NSLogs, so that we can take a look at it in
| | 06:54 | a moment. What is the temporary
URL of a video before we save it?
| | 06:58 | Carrying on, I am going to use the new
AssetsLibrary.framework to help me save this.
| | 07:04 | I am going to be saving this not into
the sandbox of my own application, but I
| | 07:09 | am going to be saving this into the
regular saved photos library that I can get
| | 07:14 | to through the photos application.
| | 07:16 | We get a handle on that
library by just instantiating the
| | 07:19 | ALAssetsLibrary object.
| | 07:21 | Now, this ALAssetsLibrary framework,
being one of the new ones, uses blocks a lot.
| | 07:26 | So when I am actually saving this video,
it's going to have a completion block
| | 07:31 | to handle what happens after the video is saved.
| | 07:33 | So I am going to create that here which is an
| | 07:36 | ALAssetsLibraryWriteVideoCompletionBlock.
That takes two parameters:
| | 07:40 | an error object, and an NS URL.
| | 07:42 | I am going to make the assumption that
when we get called, we will have saved
| | 07:47 | that file, and we will have
another NSLog message saying Success!
| | 07:51 | The new URL is so and so.
| | 07:53 | Now, this code is not being called yet.
| | 07:55 | We are just creating this block, and we
are going to pass it into a call to the
| | 08:00 | ALAssetsLibray object.
| | 08:03 | So that's where we do it;
| | 08:04 | saving the video here.
| | 08:06 | We call writeVideoAtPathToSavedPhotosAlbum,
passing URL that we had grabbed up
| | 08:12 | on the first line here, and then
passing in the completionBlock to say what
| | 08:17 | happens when it's been saved.
| | 08:18 | Once we are done with that, we can
release the assetsLibrary, and then we
| | 08:23 | can dismiss the picker.
| | 08:24 | I am going to save this.
| | 08:27 | I am going to build this project;
| | 08:30 | build has succeeded.
| | 08:32 | Now, of course, what I can't do is test
this in any meaningful way on the simulator.
| | 08:37 | So I am going to run it on my own device,
and just take a couple of screenshots
| | 08:41 | to prove whether it's working.
| | 08:44 | So I will click Build and Run.
| | 08:45 | I will open up my console, so we
can see any messages that we get.
| | 08:52 | It's installing on my machine.
| | 08:53 | I have got the one button has
come up, and I am hitting record.
| | 08:57 | It's passing a message out here, which
has nothing to do with me about using a
| | 09:01 | two stage rotation animation.
| | 09:02 | We can just leave that alone.
| | 09:04 | What I am actually going to do with
this, I am going to flick over and see my
| | 09:09 | Organizer here, and just take a quick
capture of the screen, which is now my
| | 09:13 | camera pointed at the
screen I am about to capture.
| | 09:17 | I am going to take some video.
| | 09:19 | I will take about five
seconds of video here, and stop it.
| | 09:25 | Now what will happen, if I take another
capture of the screen here, is that the
| | 09:30 | screen itself is going to throw up the
video that I have taken, allowing me to do a
| | 09:34 | quick trim of it, and allowing
me either to Retake or Use.
| | 09:37 | This is all controlled by the
ImagePickerController itself;
| | 09:41 | I don't have to bother about this.
| | 09:43 | The issue is I am going to hit the
button that says Use, and that should
| | 09:47 | call the method of my AssetsLibrary to
actually save it back into my saved photos library.
| | 09:54 | I hit that button, and I get dismissed and
just taken back to the regular part of my screen.
| | 10:00 | So if I come back over and see my
console, I should have my messages here that
| | 10:06 | will actually tell me two things:
| | 10:09 | One was the first message saying that
the temporary URL was, it gives me a file
| | 10:15 | path with a created name
with a unique ID added to it.
| | 10:19 | Then we pass that URL to the
AssetsLibrary, and said, save this properly,
| | 10:24 | and now the new URL is in the assets-library
location with an asset.MOV with a unique ID in it.
| | 10:32 | So it seems to have worked just fine.
| | 10:34 | Of course, at this point, you might be
taking that URL and saving it as a piece
| | 10:39 | of data in this actual application,
so we can bring it back later.
| | 10:43 | But this is the method that we can do
to fairly easily start to take and save
| | 10:48 | video from our own
applications into the photo library.
| | Collapse this transcript |
|
|
7. Creating Apps that Run on Multiple DevicesDetecting device capabilities| 00:00 | Even if you're intending your app to
be only used on iOS 4, do remember to do
| | 00:05 | runtime checking of the device capabilities.
| | 00:08 | It's very easy to assume that everyone has
an iPhone that's configured the same as yours.
| | 00:12 | The main things to check are camera and
multitasking support, because they vary the most.
| | 00:17 | If you're working with the
camera, you do, of course, have the
| | 00:20 | UIImagePickerController, and you can ask
a lot about the hardware based on this.
| | 00:24 | You can find out what kind of camera is
available, if a camera is available at all.
| | 00:29 | Of course, if you have the
iPod touch, there is no camera;
| | 00:32 | there's only access to the saved photos roll.
| | 00:35 | Even if you do have a camera, you don't
know whether you can take video on it.
| | 00:40 | So you can ask the
availableMediaTtypesForSourceType, does it have video, does
| | 00:44 | it have still images?
| | 00:46 | Beyond that, you can ask things like
is the flash available for the camera
| | 00:49 | device, if that makes a
difference to you, and even things like the
| | 00:53 | videoMaximumDuration, how much video can I
take if I'm building that into my own application?
| | 00:59 | When it comes to multitasking, there
is an isMultitaskingSupported Boolean on
| | 01:04 | the device, which you can look at,
though it doesn't exist on pre-iOS 4 devices.
| | 01:09 | So, in some cases, first you have to
check for the existence of this Boolean
| | 01:14 | before you can check to see what it is.
| | 01:16 | Apple's official suggestions for how
you do this is that you're first going to
| | 01:20 | grab hold of the currentDevice, you're
going to create your own Boolean called
| | 01:25 | backgroundSupported, and you'll do
something like ask if the device
| | 01:29 | respondToSelector colon and then
passing in the at sign selector
| | 01:33 | isMultitaskingSupported.
| | 01:35 | This is really asking, does this Boolean
variable exist at all before I ask what
| | 01:41 | the value of it is, and if it
does exist, we can set that to our
| | 01:45 | backgroundSupported area.
| | 01:47 | Now, this is a pretty good model for
quite a few of the capabilities that may or
| | 01:51 | may not be running on the device.
| | 01:54 | However, if you do decide to only
support certain features, and even want to
| | 01:58 | prevent people from running your
application if they have a different setup, you can.
| | 02:03 | In your Plist, there's a
UIRequiredDeviceCapabilities key, where you can add
| | 02:09 | an entry specifically requiring or
specifically prohibiting a whole bunch of
| | 02:14 | different very specific features; in fact,
the available options are quite a few.
| | 02:19 | So if you wanted to say, for example,
that you had to have the microphone or
| | 02:24 | that you wanted to prohibit your
application from running on people with an
| | 02:28 | auto-focus camera, you could do this.
| | 02:31 | But in general, you'll leave this entry alone.
| | 02:34 | You don't have to specify
the things you'd like to have;
| | 02:38 | you only put something in that key
if your app absolutely must not run
| | 02:43 | under certain conditions.
| | 02:44 | It either must have
telephony or must not have telephony.
| | 02:48 | Otherwise, you just leave that key
alone and do some basic reactions to
| | 02:53 | different hardware support
within your program itself.
| | 02:57 | Bear in mind that during the app store
submission process there are also options
| | 03:01 | to restrict your app to certain devices.
| | 03:05 | But more than anything else is the
obvious: test, test, test, because if you
| | 03:11 | don't, Apple may, and they have a
pretty good process for detecting
| | 03:15 | incompatibilities between what you say
you support and what you actually do.
| | Collapse this transcript |
| Using weak linking to new frameworks| 00:00 | Now, these days, Apple aren't even
accepting submissions to the App Store that
| | 00:04 | are built on versions of the OS
prior to 3.2, but we could still have
| | 00:09 | legitimate reasons for building
applications that we want to run on both the 4.0
| | 00:13 | with the extra new frameworks, and on
3.2 without them, say, such as the iPad.
| | 00:18 | Now typically, this idea isn't going to work.
| | 00:21 | For example, if I create a New
application, I will call it BothDevices, because
| | 00:26 | I want this to run on both devices.
| | 00:28 | I am going to expand this, and then I
am going to link to one of the frameworks
| | 00:33 | that's only in iOS 4.0.
| | 00:35 | I am going to pick the Assets Library.
| | 00:40 | That's only in the 4.0 version of the device.
| | 00:42 | So, let's say I select my simulator
here, and what I am going to do is just
| | 00:46 | click Build and Run.
| | 00:47 | I'd expect this to build
successful and install on the simulator.
| | 00:52 | It's not going to do anything,
but it installs okay. Looks good.
| | 00:55 | What I want to do is have this
code also install and run on an iPad.
| | 01:01 | There are a couple of things I
have to do to get this to work.
| | 01:05 | Now, a couple of different ways of
getting there, but the way I prefer is
| | 01:08 | first I am going to go to my project
here, right-click and say Get Info and
| | 01:14 | on the Build section, I have a whole bunch of
information about how this project is built.
| | 01:20 | Now oftentimes, people jump straight
to this Base SDK thing that currently
| | 01:24 | says iPhone Device 4.0, and we
think, well, could we switch it to 3.2?
| | 01:28 | And the answer is absolutely not,
because I am wanting to link against the
| | 01:32 | Assets Library, which is in 4.0.
| | 01:35 | So the Base SDK here is not really the
lowest thing that we are going against.
| | 01:40 | This is the optimum one
that we are going against.
| | 01:43 | However, what I can do, if I come
further down in here, is what I am going to
| | 01:48 | find is a section that allows me to
select the Targeted Device Family, just the
| | 01:54 | iPhone, the iPad or the iPhone/iPad -
| | 01:55 | that will do - and what's called the
iPhone OS Deployment Target, which is, okay,
| | 02:03 | we compiled or built against the 4.0,
but what should the code load on?
| | 02:08 | And I am going to select here 3.2.
| | 02:11 | I am going to then close this window,
and just hit Build and Run again.
| | 02:18 | I shouldn't expect any changes.
| | 02:19 | We are still opening up in the iPhone
Simulator, but what I can do now is go
| | 02:24 | over to the dropdown and say that I
want to switch to the iPad Simulator.
| | 02:29 | Now, one of the things you will often
see is if you hit the Option key, you will
| | 02:33 | see more options in there.
| | 02:34 | But I'd hope with those changes that
from the regular dropdown, you should see
| | 02:38 | iPad simulator, and I am
going to click Build and Run here.
| | 02:41 | That didn't seem to do much.
| | 02:46 | In fact, it looks like it's
completely just dumping out right away.
| | 02:50 | Even though technically it built, the
program is not running on the iPad, and if
| | 02:54 | I open up the console, we are going to see why.
| | 02:57 | We have got a message popping up
here that the Library is not loaded.
| | 03:05 | The Assets Library, image not found.
| | 03:07 | Now, depending on how the
configuration is done, you will find a variety of
| | 03:11 | error messages for this, but this
is not really a particular surprise.
| | 03:15 | We are trying to load the Assets Library,
and the Assets Library does not exist
| | 03:19 | on the iPad Simulator.
| | 03:20 | This is 3.2, and it only exists in 4.0.
| | 03:24 | So, what can we do? Well, step one;
| | 03:26 | I am going to go back into my Xcode project.
| | 03:29 | What I need to do is say that the
AssetsLibrary.framework is Weak Linked, really
| | 03:35 | saying, if you've got it, use it.
| | 03:38 | If you don't, just forget
about it; just ignore it.
| | 03:41 | The default is for all our frameworks to be
required, and they must be there for this to run.
| | 03:47 | So I need to change the
settings of this one to be Weak Linked.
| | 03:50 | And the way that I am going to do it is
I am going to find my Targets section,
| | 03:54 | and expand the Disclosure Triangle and
highlight the name of the application.
| | 04:00 | Now, there is a couple of different
ways of getting here too, but this is the
| | 04:03 | most straightforward, I find.
| | 04:04 | And come down and say, Get Info.
| | 04:07 | On Get Info for the Target,
| | 04:09 | I am going to switch to the General tab,
where we see all our Linked Libraries here.
| | 04:13 | And you will see that all four
of them are required right now.
| | 04:16 | I am going to change Assets
Library to Weak. Close that.
| | 04:22 | Another place you will often see those
settings is actually when the Target is
| | 04:26 | correctly highlighted, you may often
see them over here, and you can actually
| | 04:30 | change them there too, but I
preferred going more directly to them.
| | 04:34 | And I am going to click Build and Run.
| | 04:36 | We open up in the simulator.
| | 04:38 | We are not doing anything, but the
application is running. Close that. Come back down,
| | 04:43 | switch over to the iPad simulator, and Build
and Run, and at least the application loads.
| | 04:49 | And if I open up the console, the last
session that's started looks like it's fine.
| | 04:54 | There is no problem there.
| | 04:55 | There are no error messages.
| | 04:57 | So, looks good. Then what?
| | 04:59 | Well, the problem is, of
course, I am not doing anything.
| | 05:02 | I am not actually using the Assets
Library classes in any way, shape, or form.
| | 05:07 | And of course, my situation is that I
want to be able to use them if I have them,
| | 05:11 | and not use them if I don't.
| | 05:13 | Now, obviously your
application's behavior has to support this.
| | 05:16 | The question is, how do we
write the code to do it?
| | 05:20 | Let's say I open up my
View Controller, and in my
| | 05:24 | viewDidLoad, I am going to write a little code.
| | 05:28 | Well, if I just do something along the
level of ALAssetsLibrary, well, first we
| | 05:32 | are not going to find it, because
I need to Import the library here.
| | 05:39 | But if in my viewDidLoad, I
decide to create a copy of this,
| | 05:45 | this is going to work fine on 4.0, but
is not going to work fine, at all, on 3.2,
| | 05:51 | and it's going to cause a major problem.
| | 05:53 | So how do we deal with it?
| | 05:54 | Well, as ever, there are actually a few ways.
| | 05:58 | One of the most simple ways we could do
is we could wrap our code in some kind
| | 06:02 | of condition, and we can base that on a
couple of things that we can find out.
| | 06:05 | One example might be the
UIDevice currentDevice systemVersion.
| | 06:10 | Now, this will actually return a string,
but it will say, in our case 3.2 or
| | 06:15 | 4.0.1 or 4.0.2, and I might
wrap my code in this condition.
| | 06:21 | So in a particular part of my code,
I will check what version I am.
| | 06:24 | If I am 4.0 or above, I will
create the ALAssetsLibrary;
| | 06:28 | otherwise, I might disable
that part of the application.
| | 06:31 | So I am just going to write that out
in a NSLog to show you what that data
| | 06:34 | looks like, and here is
another more direct way of doing it.
| | 06:38 | I am going to actually write a IF
statement that's asking If NSClassFromString,
| | 06:44 | now this is a built-in function, part of
NEXTSTEP, and it basically is asking, is
| | 06:50 | there a class with this name?
| | 06:52 | If this returns nil, then it
doesn't know about an existing class
| | 06:56 | called ALAssetsLibrary.
| | 06:58 | So, if it's true, I could go ahead and
create that, and in this case, I will put
| | 07:03 | out a message saying, Yes, it exists,
| | 07:05 | create the Classes and use them;
otherwise, I am going to do an output message
| | 07:09 | that says, No, it doesn't.
| | 07:11 | I am not going take you much further than this;
| | 07:13 | you should be able to get the point
that pretty much you are going to have to
| | 07:16 | wrap a lot of code in
conditions. Is that annoying?
| | 07:19 | Oh, you betcha!
| | 07:20 | Absolutely! It's very annoying code to write.
| | 07:22 | But at least we can write it.
| | 07:24 | We don't have to guess at it.
| | 07:25 | I am going to save this and build, and
then I am going to run this on the iPhone
| | 07:31 | simulator and open up the console here,
and what I'd expect to see is exactly
| | 07:37 | this, that I see version is 4.0.1.
| | 07:41 | The class ALAssetsLibrary exists,
and I could go ahead and create it.
| | 07:45 | If I stop this, clear the log again,
switch over to the iPad simulator, and now
| | 07:53 | because we are doing Weak Linking, and
we are also checking for the existence of
| | 07:56 | the class, what I'd hope to see, with the
console open, is that version is 3.2 and
| | 08:03 | no, the class doesn't exist.
| | 08:05 | So I wouldn't create it.
| | 08:06 | I might disable part of the application.
| | 08:10 | So, while writing this conditional code
is not the most fun thing in the world,
| | 08:14 | if you are trying to create one
application that does support multiple versions
| | 08:19 | and is smart enough to
turn pieces of itself off,
| | 08:22 | this is the way to do it.
| | Collapse this transcript |
|
|
ConclusionGoodbye| 00:00 | Thanks for joining us for iOS
4 App Development New Features.
| | 00:04 | You've now seen many of the new and
improved features, frameworks, and tools
| | 00:08 | found in iOS 4, and you should now have
a good idea of what parts of iOS 4 you
| | 00:12 | want to take further.
| | 00:14 | You might want to do more with motion, or
dive deeper into video and audio, or see
| | 00:18 | what can be done with multitasking, and
also how to make apps that not only use
| | 00:22 | these new features, but degrade
gracefully for older devices when necessary.
| | 00:27 | Keep a regular eye on developer.apple.com.
| | 00:30 | In the early days of iOS 4, it's
updating with new examples all the time and
| | 00:35 | best practices on using these frameworks, but
it's a great time to be an iOS app developer.
| | 00:40 | Good luck with your applications.
| | Collapse this transcript |
|
|