Museum Tours 5.0 is out – on app store!

Compatible with the newer iOS, offering so much on the platter, it’s officially out!

With this version, Museum Tours officially targets homeschooling audiences and teachers alike. With as many as six most important Museum collections, including Science & Anthropology ones, it is set to become the first app you want to browse each morning to get your daily dose of World Museums. Simply because these collections don’t come as subscriptions, they come as just one time purchases!

For those curious arts scouts, this is the only app that offers most number of Museums across the world. Yes, Museum Tours has an unlimited collection that offers 2000+ Museums – as many as Wikipedia can offer.

And, let’s not forget, Museum Tours is available in French, German, Portuguese, and Spanish language too!

Along with Flickr, I am planning to bring more relevant set of Museum artifacts during forthcoming updates. There is just so much possible with newer set of iOS 8 APIs, one needs to prioritize what to offer and what to wait for!

iOS 8 – is it the end of an era for indie mavericks?

iOS 8 – It’s out, officially. And things are never the same.

Indie iOS Dev – the long time victim:

Indie iOS developers find themselves at the crossroads of something radical. While I don’t go far to conclude things are worsening, things are definitely evolving.

Whenever evolution takes place, old species must mutate, or make ways for the newer ones.

This time around, (again!) it’s likely to be the indie dev community. To everyone who has slightest idea, this is an ever-shrinking sailing ship since iOS inception, not necessarily pitted against steam powered boats or anything of the cruise class. But as everyone knows, iOS Appstore is the pinnacle of the crudest capitalist inequality on the planet. In other words, rephrasing the 80-20 principle, 1% of the app publishers draw away 99% of the app store revenue – the majority comprising the indie devs.

The irony is – it’s this community that keeps app store dream alive, eventually keeping the app market afloat on the planet.

Because more than God, it’s the faith in God that is important. The loss of this faith scares even the priests, like hell.

-Anonymous

Indie devs bear the hardest blows, either from big boats or the never-yielding tides. On the surface, it seems it doesn’t change anything for all those super-excited fanboys and gamers. The laws of evolution automatically pushes better boys to the top, who in turn keep indie dream alive: If someone belongs to the top lists, she deserves to be there. Following the laws of free market, customers must be happy too, for they are the ones who have pushed the biggies towards where they stand today.

However, when a quality indie fail, there are some side effects waiting to emerge after some time. One of those were demonstrated pretty well during the flight of Flappy Bird. Yes, the entire app-cloning industry relies on indie devs who made an ingenious piece of software but simply failed to secure the biggest validators of their success – money and fame.

The result? They make app (& game) templates of successful apps made by themselves, or someone else. Then they sell them for good (but not great) money. Copycats purchase them for a dime (OK, $99). Clones flood the top lists. Gamers enjoy, but only for a while until the charm lasts, and the entire mania recedes. The app clones get pushed towards the bottom. But by the time, cloners have already made more than what they deserved, due to sheer volume of the store. And there is nothing built inside app store algorithm to push quality indie devs further up the chart when clones endure the fall.

The end result? Users get the shit. App store will keep inflating till a point when the bubble will burst. Note the dilemma – the app store, and not the iOS itself, will take the plunge. Lest someone big really makes something worthwhile that can keep the flock invested.

Either way, the indie dev who made the original piece never gets his due – neither money, nor the fame. All wasn’t lost, however.

Until iOS 8 happened.

Precursor – the predator in disguise:

iOS 8 was a change waiting to happen. But it’s preamble was set with the release of iOS 7 – which brought forth major shifts such as a thing called skeuomorphism - a shift from the world of bevels and shadows. And numbered became the days of UI experts who made our apps truly outstanding, in literal sense. Off course, gaming industry will keep employing them heavier than ever, but as far as app UI design is concerned, the creativity domain of a UI designer will become fairly limited.

Apple also became sensitive towards its role in enterprise domain by introducing iBeacon in iOS 7. Being dependent on big chains for its adoption, this is one more domain where little can be done through indie dev’s will (but definitely not without his skill).

Even bigger shift in direction came via Spritekit. And to understand it, one must go back to foundation days of iOS. When I was new to iOS world, I was continually amazed how so many 2D and 3D games flooded the app stores despite steep learning curve of OpenGL. And I didn’t have to bury my head into books before I knew there existed a framework called Cocos2D that filled the gap between a game designer’s scene and complex shaders of OpenGL.

With the introduction of Spritekit, the entire ilk of Cocos2d evangelist would be rendered useless. Not that they wouldn’t find something worthwhile to do; they will, for sure. But few novice gamers would be eager to know about them, and even fewer people will realize how an entire Apple framework was conceived and born through their sheer indie will.

iOS 8 – predator or savior?

With iOS 8, all this evolved into its fullest metaphysical sense of existence. iOS 8 completed full circle of many changes that iOS 7 initiated. In parlance of software source control management:

iOS 7 can be termed as a build; iOS 8 is a version. And a major one.

Extensions brought in the long-sought customizations into UI Design. This will bring so many developers into the mainstream, who earlier had to develop their own extension frameworks - sometimes just to release their apps on Cydia-guild app stores, instead of Apple’s own. Extensions in iOS 8, especially aimed at app bundles instead of single apps, would benefit big studios more than indie ones who have one or two high quality apps at max. The app audience would surely benefit, but the steam would be lost, with sophisticated APIs to assist Swift newbies. And all those Objective-C veterans who had it the hard way would sit and watch.

That reminded us of Swift. There are few meek voices raising their concern about the very reason of Swift’s existence. Except for Apple. Yes, yet another block of clueless programmers waiting to create mobile apps would jump in the Apple queue. Not that they aren’t welcome.

Everyone who entered the programming foray had it easier than their ancestors.

With Swift’s oversimplified programming constructs (still arguable), many hypotheses would be laid to rest. One of them being: ‘An app developer should be a programming veteran having stronghold on OS, resources and memory management’. Perhaps it was Apple’s way to pre-defying any possible competition in iOS app code generation. But it may turn itself into a war between Objective C veterans and the new age Swift developers where the later may have an unfair advantage of better hardware and simpler structures. True, same argument can held between assembly and C++ camps, but C++ still maintained the need for software programmers’ understanding of hardware capability and sensible class design. With Swift’s super-easy programming constructs that are never destined for crash, the road will be over-smooth, 6-lane wide and without any apparent traffic. While such roads improve driving experience, overspeeding is inevitable; and innocents are victims most of the time, if not always.

Cloudkit is forever set to obliterate many use cases for 3rd party back-end. This is one of the changes that deserves an extra article. And to be fair, it is something Apple did in favor of indie devs, as far as intention is concerned. With 50 GB of storage incentive, it is lucrative enough for newcomers to defy any server side development and research on external APIs. While it may lure indie devs with almost free data storage, they would need to upgrade themselves if they want their cloud to be smart enough to handle complex application logic. At the same time, all those upgrades will happen inside Apple’s cloud walls, so the community would be at a loss. Smart code, but chained.

Healthkit is one more feature that is likely to drive niche developers – someone having some medical background in addition to programming. Considering the research and resources this field requires, and the regulations it entails, this is more likely to attract enterprises who are already into the foray, rather than indie programmers.

Since 2010 with retina screen of iPhone 4, there came a herd of Photo-filter apps, most of them relying on open source third party SDKs to create creative effects. In the absence of such SDKs, mavericks often wrote them. The whole effort gave us some really awesome image processing algorithms, both using Core Image as well as OpenGL. But this trend will go downhill after the introduction of Photokit. New programmers, especially the ones who joined the Swift bandwagon, are never likely to know about those awesome third party SDKs, and the hours of perspiration spent for the benefit of community.

Conclusion:

  • Like any other feature rich update, iOS 8 will set the tone for newer kind of apps. However, these changes being in favor of large scale enterprises or Apple itself, indie devs are going to take the blow.
  • Change is inevitable. Earlier you embrace it, the better. Indie devs must suit themselves to iOS systems aimed at enterprise, for only they can churn out utilities from frameworks. And while doing this, keep the torch of community contributions alive, to keep the app world livable. Advice? Hope for the best, and never be unprepared to face the worse.
  • iOS (objective C?) veterans aren’t an endangered species waiting to be rescued from winds of change. What needs to be rescued is their undying attitude and will to change things for future.
  • Despite it’s radical set of innovations, iOS 8 is not declaratively good. On the other hand, it’s not bad either. The only thing that is sure is, it’s different. The change doesn’t ooze freshness as it should, for now.
  • Despite all the pessimism around indie devs who cannot sustain motivation to create and contribute, there exists torch-bearers who have kept the flame burning. Till this flame lasts, the programming passions will keep burning and enlightening the universe.

For all we know for now, iOS 8 is an evolution that gives us new weapons, at the cost of killer instinct.

World Museums is here…

Finally, World Museums – a mega project for art enthusiasts around the world is materialized – and available on your iPhone and iPad around the globe.

While it is true that app store has few high quality Museum apps that allow users class Museums around the world on their glimmering device screens, there loomed a huge gap that art enthusiasts, students and teachers felt alike – the data. And World Museums tries to bridge this gap by providing both online and offline access unlimited number of Museums – be it your home, class or cafe.

World Museums presents Museums from across the world from the most reliable source – wikipedia. But wait, Wikipedia is accessible everywhere. What’s new?

Quite right. But when you truly feel like browsing an eye-catching Museum collection, you don’t go on Wikipedia and rely on its search. You want something that’s on hand – that’s the initial thrust that this app provides to Museum enthusiasts. And it’s big one if you think of your children or classroom students. Next time they ask you “What a Museum looks like?” – you have an answer at your fingertips.

Once on your iPhone or iPad, you could do whatever with it what you expect to do with real museum: See some of its artifacts, check out it’s location on the map, browse its website and browse its wikipedia. See for yourself:

Free version, along with quality ads, provides full access to limited number of Museums. Unlocked (ad-free) version provides unlimited downloads, up to 50 at a time. After downloading a millennium from wiki, if you are smitten by recollection surge, you could go back, delete the whole stuff, and start over again. That’s unlimited flexibility.

The audience of this app is not only teachers, students, parents and art lovers – it’s aimed to hook up anyone who hasn’t been a Museum enthusiast previously.

After all, isn’t it what Museums have done to Science, History and everything else, so far?

How to get app designer

I could very well title this post as “How to design your iOS / Android app and make it the most unique one” (Maybe that would sell it for $49? Or $399?!)

But I didn’t, due to two reasons.

  • I am not fond of jumping into books for anything that can be realized through senses. I do it only as a last resort. As a result, I haven’t read any books on how to do great UI / UX. I would rather focus on what lies ahead of me, and what kind of layout and design principles would suit it. Based on it, I would read up the necessary skills to churn out what I think is best for it.
  • As anyone can guess, in the Apposphere, there are quite fewer people who would want to design the best, than the crowd who would rather want to outsource the task to the best available guy. To decide who is the best is subjective; however, this article could throw some light.

App Design, like development, is an iterative cycle. (for that matter, what life is, btw?)) Let’s jump into our first run.

Enough with verbiage – How to get an app designer (and get the best out of him):

Design, contrary to what many think, comprises not only of UI or UX but primarily of data. The data. Your app revolves around it, unless you are making something as simplistic as iPhone Light or something as stupid as the next “Shout at your boss” app.

Having said that you are making a data-centric app to solve some unique problem that the world is facing, again, 2 questions: How do you structure your data, and how do you want user to see it?

How to design your app requirement?

Say you want a simple app listing hospitals, their surgeons, diseases they cure and addresses. Internally, you can have a flat-structured database table (or better, an API endpoint) that comprises every detail as a field. For this discussion’s sake, we are taking this data structure for granted, because most app publishers nowadays start with a strong data premise, be it native or cloud based.

Having fixed the data source, there are number of ways you can present it.

A) If you want it categorized by disease they cure, the first screen would list category of body parts (heart, bones), which would lead to a list to diseases (heart attack, arthritis) – and from here user can browse list of hospitals, their doctors, their contact details etc.

B) On the other hand, if you want to show geographically nearby hospitals, user should see and be able to select their location first, then the hospitals, then the diseases they cure. Body part listing maybe redundant here.

Note that by following the Approach A, you are creating an app that is more useful to curious souls, who would start with an academic approach, and get to practical details later. Needless to say, Education would be the App store category for listing your app.

On the contrary, if you follow Approach B, you provide services to anyone who wants immediate medical help.You could list your app under Medical or Lifestyle category. While there would be much wider audience for this kind of app, there must be equally drastic number of competitor apps available too. To begin with, even Google maps would be your competitor.

But that should not discourage you, as there are many more apps for given function which are quite doing well. Differentiator? UI and UX.

Now, go back to our approaches A and B. How would you present them in your iPhone app? Here are the possible solutions, respectively for A and B:

C) You would normally use a table of body-parts, followed by table of disease for each of them, which would be followed by list of hospitals curing them, followed by list of surgeons + contact details.

D) Your most logical choice of first screen would be a map showing nearby hospitals. After choosing one from map, you can show other details on forthcoming screens.

Note that in depicting C or D, I have taken the most simplistic approach. If you have grilled yourself enough on your own idea, you must already have versions C.5 or D.10. And if that is the case you may no longer have to read further.

Is it not the case? Go on.

So how would you choose your app designer?

You should definitely have A or B in mind. If not, you need further work. In other words, if you don’t know your potential market and also don’t know what is it that you want to solve for them, you are in for waste of time, money, and worst, the motivation to publish an app.

Once you have your top level objective done, go to iTunes store and check what competing apps are doing, what are their bottlenecks, how you can beat them and what are their stronger areas that you need to consider / ignore. Once identified what problem you want to address, decide the solution. Go back to your desk, grab a paper and pencil and, in the worst possible way, sketch out what your app would like.

Once done, carefully word your requirements in no more than 5 lines. Note that A and B noted above are your help. For our example of Hospitals app, it could be somewhat like (using Approach A):

Need an app designed that would-

  • Show diseases by category of body parts
  • List out hospitals that cure them
  • Also show hospital details (list them as needed, like phone number
  • I already have app graphics and database / I want them designed too.

Now, float it around. Internet is full of outsourcing boards, and you may choose whatever is your favorite. You will get flooded with boilerplate pitches, countless newbies, and if luck and budget favors, few experts. But you need to separate wheat from the chaff.

Once you have people to screen, you can start talking to them. You should have a vague idea about C or D, but you should let the candidate tell you about it.

  • Can he sum up your data model? Can he come up with number of data tables? If it is a webservice endpoint, does he identify parameters to the list or detailed query? Does he ask questions about it?
  • Most important: Does he come up with C or D approaches himself?
  • If not, does he even offer you an idea that you could visualize?
  • When in doubt, ask for a visual (even poor mspaint sketch is enough) instead of verbiage.

If your candidate could not get any of the above things right, you should move on to the next one. Remember to approach each one with fresh mind: pose right questions, hold no bias from your previous interview nightmares, and understand how things are said. Also remember that like every other skill, good design is not legacy of people limited to certain geographies. Expect to find the best from any corner of the world, and be prepared to pay in line with the market value for the category you have hired from.

Innovation:

The approach that our Hospitals app example followed was the basic approach. And it converted the app idea into design using the most simplistic symbols i.e. table view for list, detail view for details, map view for location and so on. For the purpose of laying out design principles, we are done. If you found your designer following it, you are off for a good start.

However, if you have immersed yourself fully into your domain, you would want more from your designer. You will not stop at simple flow diagrams and standard iOS controls to present your content. iOS App Store racks have not much space left for even subjects as advanced as Artificial Intelligence. If you want to lead or trounce your competition, you should have better judgements about how you could -

  • Simplify data model so that your user is not flogged with details (what was the last time that you browsed “Google.com” homepage?)
  • Enrich the visual content so that your user gets the most in least possible time (again, this pertains to app design, not game design, where you want user immersed for as long as possible)

Both points are really subjective. And they require your, the app owner’s decision.  Is your designer able to offer you choices here? As always, in order to get the right answers, you must ask the right questions.

Consider you are making a News app. It would be logical to list categories such as Sports, Science, US/Europe, Business etc.

But presenting a screen like that as the first screen would be quite a clique, which offer little functional or aesthetic value to the user. Besides, there are hundreds of news websites that are just built around that idea.

Someone realized that while jumping into a News app on a dreary morning, users are expecting to have quick bite of headlines – and many more of them. Pulse app, which is now part of LinkedIn, was designed by Indian students at Stanford. I mention it because it explains blending of the above two innovation objectives in the most striking way. By dissolving News Categories into table view sections, they eliminated an entire layer that user had to cross to get to the news item. That way, users got instant access to what they were looking for, along with eye-catching collection of  news items from popular sources, laden with high quality images. Result? In 2011, Pulse was selected as one of 50 apps in Apple’s App Store Hall of Fame, and was also named one of TIME’s top 50 iPhone apps of the year. What’s more, Steve Jobs presented it in his keynote.

Blogosphere became flooded with tutorials on how to design Pulse like interface. It was realized that many intermediate level iOS programmers could come up with that kind of interface. But to realize it, designers had to envision it first. And for the designer to come up with that kind of solution, someone had to ask for that solution.

And more importantly, invent the problem.

iOS Tutorial: Create iPhone video chat app using Parse and Opentok (tokbox)

Disclaimer: Creating your first iPhone video chat app isn’t a rocket science, but it is far from building a sandcastle either. As a result, this is a very lengthy tutorial. A 6000+ word mammoth, if you ask. And I saw no reason to fragment it into episodes. Serve yourself with a cup of your favorite beverage, and proceed at your own risk. (hint: Nothing beats coffee when it comes to headaches.)

Disclaimer: Are you an anxious googler who doesn’t like lengthy tutorials? Here is the entire project. (Licensing details are given at the end) But you won’t be able to run an iPhone video chat app just out of it. Go figure yourself. If you don’t want to, go on to read.

In a market flogged with social networking apps, Video chat is a killer weapon that you can wield to ace your rivals on the App store in one go. According to study, 93% of human interactions happen through visual interface.

What the hack. Need to chat face-to-face doesn’t need statistical justification. But how do they work? The question is more of design than of technical implementation in a world full of third parties. If you are scared of the word mammoth, you need to live with it because that’s the name of beast we are dealing with.

By the way, here is a warm-up snap of the end result of this iOS tutorial:

iPhone video chat

Notice the mammoth, and don’t be scared because step by step, we are going to dig its grave. And don’t pay attention to the monkey, at least for now.

The Basics of a video Chat app:

While leading video chat providers do it with their own streaming servers, cloud solutions have come to fore to assist small to medium app providers – helping them have their app “out there” quickly while taking pie off their growing revenue on per request basis. Opentok (http://www.Tokbox.com/) is one of those few pioneers that provides a nice, easy-to-integrate SDK for your next iphone app to have video chat feature.

While Opentok provides their iOS API and iOS SDK with two great examples of iOS implementation (here and here), there is still one piece left: User management. Opentok provides nice platform for video chat, but between whom?  Let’s take a deeper dive into how streaming actually works between users of services like Skype, MSN, and Yahoo video chat.

A streaming server works on fundamentals of an Http web server: In a pretty raw representation, all it recognizes is request and response, without caring who sent it. An Http server does just that. It does not worry about states, userID, password or any such access token mechanism. In other words, session management is absent.

The scenario changes when there arises a need to identify the user, authenticate him, and keep him authenticated during his entire browsing experience. A server needs to remember a user, and whatever other entities that come linked with him. These requirements become more stringent while developing a mobile software like iPhone video chat, where identities are quite crucial – they can make or break the authenticity of your app. In large web portals such as Yahoo.com or Amazon.com, dedicated authentication servers generate unique identity for users, and supply application servers with these goodies so that they can track the users until they log out.

To perform its task, a streaming server relies on only one entity to recognize who sent the request and whom to respond – this entity is session ID. It does not recognize or want to care who generated it. As far as it receives a valid sessionID, it keeps replying to streaming requests. Any user with a valid session ID is, in principle, automatically entitled to view other user’s feed who is also using the same session ID. Session IDs are of the form:

2_MX4yNjU5MzIyMn5-VHVlIEFwciAyMyAwMjoxMTo0NCBQRFQgMjAxM34wLjEzMTIwMTYyfg

You definitely know where this is going: To keep track of who wants to connect to whom in a more real-worldly way, like we do in Yahoo Messenger or Skype, we need another server that keeps track of users. User management is essential in any social networking app. It depends on you how much you want to do it – you can store big data including address, phone numbers and likes, or you can pick 3-4 fields of your choice to make it easier on the user. But the fact is you need to do it. Any software that relies on user interaction cannot do without it – and so is our iPhone video chat.

To handle user management, there are number of cloud solutions available again. For the purpose of this tutorial, I have chosen Parse.com because it boasts of 60k+ apps live, claims easiest of the APIs, and those claims, as I have experienced, are right!

Some Trivia: As I am typing this, Parse.com has been acquired by Facebook. Irrespective of whether this is a good omen, I am continuing to scribble this.

The objectives of this tutorial, therefore, are to show:

  • How to enable Parse.com users see each other (Your favorite messenger’s who’s online sort)

  • How to make them talk with each other using Opentok video streaming feature in your next great iphone video chat app

Disclaimer: I do not work for Tokbox. Neither for Parse.com (or, for that matter facebook!). Then why am I writing this tutorial? There already exists a nice attempt to explain the same concept: Broadcast tutorial by Tokbox itself.

Need for writing an entire iPhone video chat tutorial this long felt like an overkill first. But it sprung from the fact that the Broadcast tutorial talks about broadcasts, where only one party’s feed is viewable to others. Two-way chat is the growing need of time. Also, looking at the Broadcast first time, I felt the learning curve was quite steep for a video chat novice programmer. I strongly felt I could simplify many concepts explained there.

My take? If you can’t understand it, explain it. Let’s go.

Setup (Parse.com and Tokbox.com) for your iPhone video chat app – LiveSessions:

My showcase app – Livesessions – requires some configuration on both Tokbox.com and Parse.com profiles – the server side. Parse.com needs your app, and so does Tokbox.com, although it names it a Project.

I assume you know enough to configure an app on Parse.com. In addition to it, you also need 2 data tables – ActiveUsers and ActiveSessions, though there is no need to pre-populate them at any point. Here is a snap of what their column lists will look like:

ActiveUsers ActiveSessions
userID – String
userLocation – Geopoint
userTitle – String
callerID  – String
callerTitle – String
receiverID – String
sessionID – String
publisherToken – String
subscriberToken – String
isVideo – Boolean
isAudio – Boolean

Similarly, on Tokbox.com, once you login, go to dashboard and create new project, it will automatically create an API key and API secret. Note that these are your credential as a Tokbox developer, not a chat user. API key is more like your user ID and API secret is a password. The resulting Tokbox dashboard screen will look like this (the real API key and text are hidden):

iPhone video chat

Tired? There is still one piece left to be done. As I discussed at length earlier, we need to connect our streaming application server (Tokbox.com) with authentication server (Parse.com). Unless this is done, streaming server has no way to know which user is calling whom because all it knows about is session ID. Our iPhone video chat user, on the other hand, only knows about his own user credentials supplied to him by Parse.com.

As it is apparent, it is Parse.com’s job to connect the two. That is:

  • To intercept logged in user’s request to chat (the caller).
  • To convert it into Tokbox session ID, get the session ID and other necessary information (the token) from Tokbox.
  • To Inform the caller and receiver (Parse.com users) about the same.

Since both users now have a session ID and a token which is received from Parse.com, they can seamlessly communicate via Tokbox streaming server.

If you think about it again, this is not very much unlike the token guy at your nearby bank. Once he hands you a token with a number on it, the public address system connects you with the next available teller counter, thus removing the token guy from the workflow. However depending on the functionality we may need our token guy (Parse.com) around a bit more time. In fact, we will need him even after the chat ends, and we will quickly see why. But for now, lets see what Parse.com does as the token guy.

chat-arch

To accomplish above, Parse.com must obtain sessionID, publisherToken and subscriberToken from tokbox, and that is where Parse cloud code comes into picture. In Cloud code section, you must upload some code so that the resulting screen looks like this:

iPhone video chat

The process of uploading (oops, deploying) cloud code on Parse.com is described in this source tutorial (Setting Up section) and here, and it is far better than I could explain here. So let’s skip it to maintain the scope. However for simplicity’s sake, I have included it step by step in readme.txt that comes within the code.

But I would take a few moments to explain what this code does, and why. The iPhone app user initiates a call to another user. No, he does not make a phone call. Remember, it’s LiveSession app’s job to handle the entire call, just like Skype or Yahoo messenger does. What our iPhone app needs to do under the hood is fairly simple task: it is saving a row to ActiveSessions Parse table we just created. There, it stores caller user ID (callerID) and receiver user ID (receiverID) among other things.

Now what this cloud code does is something quite magical, yet simple. It intercepts the Save operation in its beforeSave cloud trigger – nicely elaborated here. From within beforesave, Opentok javascript API takes over. Opentok supplied function createSession generates a session ID. Another function, Opentok.generateToken, creates a publisher token or subscriber token, depending on the role argument passed, which decides whether you want to publish your own video feed (Opentok.ROLE.PUBLISHER) or see other user’s video feed (Opentok.ROLE.SUBSCRIBER) using that session ID. (It is unclear from my experience if there is any difference between the two. For the scope of this discussion let us generate both as we need two-way video feeds anyway.)

Since we must remember we are within beforeSave trigger, we already have the handle to the object being saved – ActiveSessions. By simply setting its respective members – we can save three distinct items to our Parse.com database: sessionID, publisherToken and subscriberToken – all three columns in ActiveSession table.

Well – that’s for that. What? You are done with the back end of your first iPhone video chat app! Congratulations!

But how? All we required for two user’s to connect via streaming server is a user id (session id) and a password (token) – and we have both. Now all we have to do is – avail them to iOS client via Parse.com. Not that it’s quite easy as 1-2-3, but the mammoth has bitten the dust already.

Serve yourself a cup of hot coffee as you wait to see one of your friends on your iPhone video app screen! Well, not quite quickly, but before you jump in, you can do them some favor: read this disclaimer if it can help any of your Android friends:

Disclaimer: The server setup that you have done on Parse.com isn’t restricted to iOS apps alone. You can use same cloud code and table structure for your Android based chat apps too, using Parse.com’s Android SDK.

iOS client – the other part of the deal:

Now that the back end is accomplished, let’s focus on how iOS client – our own LiveSession iPhone app – keeps its part of the deal. The major tasks we aim to cover are:

1) Initiate iPhone video chat call – by saving an ActiveSession row (we already described the server part of it above as part of cloud code)

2) Handle Incoming Call – check Parse.com database for an incoming video call – described down the line.

Let’s tackle each of them – one by one. But first and foremost, let’s setup the basic iOS project and go over what’s all needed.

iPhone Video Chat Initial Project Setup:

Open XCode, setup a single View application with default options. Name it LiveSessions. In storyboards, set up 2 scenes: One for users list – named LSViewController (derived from UIViewController), and another for hosting video chat view, LSStreamingViewController (again, subclass of UIViewController).

Next, add a UITableView object to LSViewController scene through storyboard. This table view must maintain a list of Parse.com users who log into LiveSession app any time. LSViewController will handle all the chores related to UITableView, nothing unusual. Do not forget to implement UITableViewDelegate and UITableViewDatasource protocols in LSViewController so as to handle table view related stuff.

In addition to the above, we also need a helper class called ParseHelper which wraps our calls to Parse. All of its members and functions would be static.

To use Parse and Opentok framework, we need to link them, as well as some of the required frameworks used by both of them.

Parse framework can be obtained from here.

Opentok SDK can be obtained from here. This link also explains at length what all you need to do in order to link Opentok framework successfully.

Next, you should add some libraries to LiveSessions by selecting it, going to Build Phases->Link Binary section. After adding number of frameworks, this section should look like this:

At the end of linking everything, your Project tree should look like this:

Project-tree

(Notice the Opentok.bundle thing that come as part of Opentok sdk. Also see three other libraries that go below it in order to link everything together).

Before we proceed, let’s have one look at how beautiful (!) our storyboard looks:

Step 1 – Initiate Video Call:

Parse.com acts as a mediator between caller, Opentok streaming engine and receiver. The first and foremost requirement is to generate sessionID, publisherToken and subscriberToken, so that both clients can seamlessly connect via Opentok once session is established. We already know the Parse.com cloud code does that. But how will LiveSessions app invoke the cloud code?

The following code not only stores an ActiveSessions object to Parse, but also invokes cloud code (beforeSave trigger) we discussed above that generates sessionID, publisherToken and subscriberToken – and they are eventually stored into ActiveSessions table itself.

//ParseHelper.m
//will initiate the call by saving session
//if there is a session already existing, do not save,
//just pop an alert
+(void)saveSessionToParse:(NSDictionary *)inputDict
{    
    NSString * receiverID = [inputDict objectForKey:@"receiverID"];

    //check if the recipient is either the caller or receiver in one of the activesessions.
    NSPredicate *predicate = [NSPredicate predicateWithFormat:
                              @"receiverID = '%@' OR callerID = %@", receiverID, receiverID];
    PFQuery *query = [PFQuery queryWithClassName:@"ActiveSessions" predicate:predicate];

    [query getFirstObjectInBackgroundWithBlock:^
    (PFObject *object, NSError *error)
    {
        if (!object)
        {
            NSLog(@"No session with receiverID exists.");
            [self storeToParse:inputDict];
        }
        else
        {
           [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:kReceiverBusyNotification object:nil]];
           return;
        }
    }];
}

+(void) storeToParse:(NSDictionary *)inputDict
{
    __block PFObject *activeSession = [PFObject objectWithClassName:@"ActiveSessions"];
    NSString * callerID = [inputDict objectForKey:@"callerID"];
    if (callerID)
    {
        [activeSession setObject:callerID forKey:@"callerID"];
    }
    bool bAudio = [[inputDict objectForKey:@"isAudio"]boolValue];
    [activeSession setObject:[NSNumber numberWithBool:bAudio] forKey:@"isAudio"];

    bool bVideo = [[inputDict objectForKey:@"isVideo"]boolValue];
    [activeSession setObject:[NSNumber numberWithBool:bVideo] forKey:@"isVideo"];

    NSString * receiverID = [inputDict objectForKey:@"receiverID"];
    if (receiverID)
    {
        [activeSession setObject:receiverID forKey:@"receiverID"];
    }

    //callerTitle
    NSString * callerTitle = [inputDict objectForKey:@"callerTitle"];
    if (receiverID)
    {
        [activeSession setObject:callerTitle forKey:@"callerTitle"];
    }

    [activeSession saveInBackgroundWithBlock:^(BOOL succeeded, NSError* error)
    {
        if (!error)
        {
             NSLog(@"sessionID: %@, publisherToken: %@ , subscriberToken: %@", activeSession[@"sessionID"],activeSession[@"publisherToken"],
                   activeSession[@"subscriberToken"]);

             LSAppDelegate * appDelegate = [[UIApplication sharedApplication] delegate];
             appDelegate.sessionID = activeSession[@"sessionID"];
             appDelegate.subscriberToken = activeSession[@"subscriberToken"];
             appDelegate.publisherToken = activeSession[@"publisherToken"];
             appDelegate.callerTitle = activeSession[@"callerTitle"];
             [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:kSessionSavedNotification object:nil]];
         }
         else
         {
             NSLog(@"savesession error!!! %@", [error localizedDescription]);
             NSString * msg = [NSString stringWithFormat:@"Failed to save outgoing call session. Please try again.  %@", [error localizedDescription]];
             [self showAlert:msg];
         }         
     }];
}

At the end of executing the above code, we should have a sessionID, publisherToken as well as subscriberToken in our Parse.com ActiveSessions table. Alright, but who will execute it? Lot of stuff still remain unanswered – for example, from where does all the argument values (receiverID, callerID) come from? We deliberately missed that part, because establishing the session was most important. The callerID, receiverID parameters that we used above are eventually just the user IDs generated by Parse.com PFUser object. You can have your own way of registering and authenticating a user. In LiveSessions, we just store each user within ActiveUsers table, and only using a user title of his / her own choice. No emails, passwords or verification. And here is code that is responsible for it:

//ParseHelper.m
+(void) showUserTitlePrompt
{
    UIAlertView *userNameAlert = [[UIAlertView alloc] initWithTitle:@"LiveSessions" message:@"Enter your name:" delegate:self cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
    userNameAlert.alertViewStyle = UIAlertViewStylePlainTextInput;
    userNameAlert.tag = kUIAlertViewTagUserName;
    [userNameAlert show];
}

+(void) anonymousLogin
{
    loggedInUser = [PFUser currentUser];
    if (loggedInUser)
    {
        [self showUserTitlePrompt];       
        return;
    }

    [PFAnonymousUtils logInWithBlock:^(PFUser *user, NSError *error)
     {
         if (error)
         {
             NSLog(@"Anonymous login failed.%@", [error localizedDescription]);
             NSString * msg = [NSString stringWithFormat:@"Failed to login anonymously. Please try again.  %@", [error localizedDescription]];
             [self showAlert:msg];
         }
         else
         {            
             loggedInUser = [PFUser user];
             loggedInUser = user;
             [self showUserTitlePrompt];
         }
     }];
}

What this does is simple: when the app launches, check for the locally stored Parse user ([PFUser currentUser]), and if one does not exist, perform anonymous login, which will create a PFUser object on Parse.com Users table. What is important to us is loggedInUser static object that we use to store currently logged on user. At the end of successful login, showUserTitlePrompt function seems to prompt the user to enter a title of his / her choice.

Fine, but what happens when user enters it? Well, significant number things. For a start, here is how LiveSessions handles it:

//ParseHelper.m
+ (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (kUIAlertViewTagUserName == alertView.tag)
    {
        //lets differe saving title till we have the location.
        //saveuserwithlocationtoparse will handle it.
        LSAppDelegate * appDelegate = [[UIApplication sharedApplication] delegate];
        appDelegate.userTitle = [[alertView textFieldAtIndex:0].text copy];
        appDelegate.bFullyLoggedIn = YES;

        //fire appdelegate timer
        [appDelegate fireListeningTimer];
        [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:kLoggedInNotification object:nil]];
    }
    else if (kUIAlertViewTagIncomingCall == alertView.tag)
    {
        if (buttonIndex != [alertView cancelButtonIndex])   //accept the call
        {
            //accept the call
            [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:kIncomingCallNotification object:nil]];
        }
        else
        {
            //user did not accept call, restart timer          
            //start polling for new call.
            [self setPollingTimer:YES];
        }
    }
}

Notice the part under tag kUIAlertViewTagUserName. This code tells LiveSessions that user is now fully logged in, along with an identification (title) of his / her choice. This title will be eventually stored into ActiveUsers table as userTitle, but with one more thing: user’s current location. Yes, LiveSessions is a location-aware app. And to obtain user’s location,  ParseHelper.m posts a kLoggedInNotification notification to LSViewController. LSViewController has CLLocationManager code inside it which will track user’s current location. At the end, once we have everything, the entire user (his title, user ID and location) are saved into ActiveUsers table.

Here is what goes inside LSViewController to obtain user’s current location, and call to Parse wrapper for storing it to ActiveUsers table:

//LSViewController.m
//Called in response of kLoggedInNotification 
- (void) didLogin
{
   [self startUpdate];
}

#pragma location methods
//this will invoke locationManager to track user's current location
- (void)startUpdate
{
    if (locationManager)
    {
        [locationManager stopUpdatingLocation];
    }
    else
    {
        locationManager = [[CLLocationManager alloc] init];
        [locationManager setDelegate:self];
        [locationManager setDesiredAccuracy:kCLLocationAccuracyBestForNavigation];
        [locationManager setDistanceFilter:30.0];
    }

    [locationManager startUpdatingLocation];
}

//stop tracking location
- (void)stopUpdate
{
    if (locationManager)
    {
        [locationManager stopUpdatingLocation];
    }
}

//this will store finalized user location. 
//once done, it will save it in ActiveUsers row and then fetch nearer users to show in table.
- (void)locationManager:(CLLocationManager *)manager
    didUpdateToLocation:(CLLocation *)newLocation
           fromLocation:(CLLocation *)oldLocation
{  

    CLLocationDistance meters = [newLocation distanceFromLocation:oldLocation];
    //discard if inaccurate, or if user hasn't moved much.
    if (meters != -1 && meters < 50.0)
        return;

    NSLog(@"## Latitude  : %f", newLocation.coordinate.latitude);
    NSLog(@"## Longitude : %f", newLocation.coordinate.longitude);

    appDelegate.currentLocation = newLocation;

    //pause the updates, until didUserLocSaved is called
    //via kUserLocSavedNotification notification, to avoid multiple saves.
    [self stopUpdate];

    PFUser * thisUser = [ParseHelper loggedInUser] ;

    [ParseHelper saveUserWithLocationToParse:thisUser :[PFGeoPoint geoPointWithLocation:appDelegate.currentLocation]];
    [self fireNearUsersQuery:RANGE_IN_MILES :appDelegate.currentLocation.coordinate :YES];
}

The first unknown in above code so far is call to fireNearUsersQuery function,which serves front end. We will come to it later. The other unknown is saveUserWithLocationToParse function, which will fill the gaps left so far to complete the back end. It belongs to ParseHelper.m, and here it goes – there is nothing unusual about storing it, and it acts as our own little user repository. The generated user’s object ID is stored for later use inside activeUserObjectID.

//ParseHelper.m
+ (void) saveUserWithLocationToParse:(PFUser*) user :(PFGeoPoint *) geopoint
{
    __block PFObject *activeUser;

    PFQuery *query = [PFQuery queryWithClassName:@"ActiveUsers"];
    [query whereKey:@"userID" equalTo:user.objectId];
    [query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error)
    {
        if (!error)
        {
            // if user is active user already, just update the entry
            // otherwise create it.
            if (objects.count == 0)
            {
                activeUser = [PFObject objectWithClassName:@"ActiveUsers"];
            }
            else
            {                
                activeUser = (PFObject *)[objects objectAtIndex:0];
            }
            LSAppDelegate * appDelegate = [[UIApplication sharedApplication] delegate];
            [activeUser setObject:user.objectId forKey:@"userID"];
            [activeUser setObject:geopoint forKey:@"userLocation"];
            [activeUser setObject:appDelegate.userTitle forKey:@"userTitle"];
            [activeUser saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error)
            {
                if (error)
                {
                    NSString * errordesc = [NSString stringWithFormat:@"Save to ActiveUsers failed.%@", [error localizedDescription]];
                    [self showAlert:errordesc];
                    NSLog(@"%@", errordesc);
                }
                else
                {
                    NSLog(@"Save to ActiveUsers succeeded.");
                    activeUserObjectID = activeUser.objectId;

                    NSLog(@"%@", activeUserObjectID);
                }
                [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:kUserLocSavedNotification object:nil]];
            }];
        }
        else
        {
            NSString * msg = [NSString stringWithFormat:@"Failed to save updated location. Please try again.  %@", [error localizedDescription]];
            [self showAlert:msg];
        }
    }];
}

The code so far ensured a user is saved inside ActiveUsers table. We also saw how he / she can initiate a video call to another user, by creating an ActiveSessions object. But whom does the user chat with?

We must also present a list of users to logged on user to chat with – equivalent of Yahoo/Skype friend’s list. Sending friend requests through email or any other means would be quite an overkill for our tutorial’s scope. To keep things minimal, we don’t even ask our users to enter their email ID for registration.

Instead, we have chosen a unique way to test out video chat feature: show list of users who are geographically within specified radii – say 200 miles. Parse.com already has PFGeopoint related query mechanism which makes our task easier.

The other unknown in code above, fireNearUsersQuery goes as below, and it fills up the datasource for the LSViewController  table view – an NSMutableArray made of dictionaries filled with user’s titles:

//LSViewController.m
//this method polls for new users that gets added / removed from surrounding region.
//distanceinMiles - range in Miles
//bRefreshUI - whether to refresh table UI
//argCoord - location around which to execute the search.
-(void) fireNearUsersQuery : (CLLocationDistance) distanceinMiles :(CLLocationCoordinate2D)argCoord :(bool)bRefreshUI
{
    CGFloat miles = distanceinMiles;
    NSLog(@"fireNearUsersQuery %f",miles);

    PFQuery *query = [PFQuery queryWithClassName:@"ActiveUsers"];
    [query setLimit:1000];
    [query whereKey:@"userLocation"
       nearGeoPoint:
     [PFGeoPoint geoPointWithLatitude:argCoord.latitude longitude:argCoord.longitude] withinMiles:miles];    

    //delete all existing rows,first from front end, then from data source. 
    [m_userArray removeAllObjects];
    [m_userTableView reloadData];    

    [query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error)
    {
        if (!error)
        {
            for (PFObject *object in objects)
            {
                //if for this user, skip it.
                NSString *userID = [object valueForKey:@"userID"];
                NSString *currentuser = [ParseHelper loggedInUser].objectId;
                NSLog(@"%@",userID);
                NSLog(@"%@",currentuser);

                if ([userID isEqualToString:currentuser])
                {
                    NSLog(@"skipping - current user");
                    continue;
                }

                NSString *userTitle = [object valueForKey:@"userTitle"];

                NSMutableDictionary * dict = [NSMutableDictionary dictionary];
                [dict setObject:userID forKey:@"userID"];
                [dict setObject:userTitle forKey:@"userTitle"];

                // TODO: if reverse-geocoder is added, userLocation can be converted to
                // meaningful placemark info and user's address can be shown in table view.
                // [dict setObject:userTitle forKey:@"userLocation"];
                [m_userArray addObject:dict];
            }

            //when done, refresh the table view
            if (bRefreshUI)
            {
                [m_userTableView reloadData];
            }
        }
        else
        {
            NSLog(@"%@",[error description]);
        }
    }];
}

The result of fireNearUsersQuery call will be somewhat like below, where 3 nearby users (<200 miles radii) are visible for chat:

iPhone video chat

Inside LSViewController, the m_userTableView gets populated from m_userArray. Each row in the table view has a green Call button. When you tap that button, call is initiated for that user as the receiver ID. What call? The code we just covered to store the session: saveSessionToParse. Who calls it? Well, now it’s time the video chat scene (LSStreamingViewController) takes charge. Before proceeding, take a look at this activity flow – the big picture. You will come back to it quite often as you read on:

iPhone Video chat

Upon tapping of the green phone call button, a segue is performed to transition to LSStreamingViewController. Inside LSStreamingViewController, [ParsHelper saveSessionToParse] is called. Here is that part:

//LSViewController.m
- (void) startVideoChat:(id) sender
{
    UIButton * button = (UIButton *)sender;

    if (button.tag < 0) //out of bounds
    {
        [ParseHelper showAlert:@"User is no longer online."];
        return;
    }

    NSMutableDictionary * dict = [m_userArray objectAtIndex:button.tag];
    NSString * receiverID = [dict objectForKey:@"userID"];
    m_receiverID = [receiverID copy];
    [self goToStreamingVC];
}

- (void) goToStreamingVC
{
    //[self presentModalViewController:streamingVC animated:YES];
    //
    [self performSegueWithIdentifier:@"StreamingSegue" sender:self];
}

-(void) prepareForSegue:(UIStoryboardPopoverSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"StreamingSegue"])
    {     
        UINavigationController * navcontroller =  (UINavigationController *) segue.destinationViewController;        
        LSStreamingViewController * streamingVC =  (LSStreamingViewController *)navcontroller.topViewController;        
        streamingVC.callReceiverID = [m_receiverID copy];    
        if (bAudioOnly)
        {
            streamingVC.bAudio = YES;
            streamingVC.bVideo = NO;
        }
        else
        {
            streamingVC.bAudio = YES;
            streamingVC.bVideo = YES;
        }
    }
}

Once inside LSStreamingViewController:

//LSStreamingViewController.m
- (void) viewDidAppear:(BOOL)animated
{
    if (![self.callReceiverID isEqualToString:@""])
    {
        m_mode = streamingModeOutgoing; //generate session
        [self initOutGoingCall];
        //connect, publish/subscriber -> will be taken care by
        //sessionSaved observer handler.
    }
    else
    {
        m_mode = streamingModeIncoming; //connect, publish, subscribe
        m_connectionAttempts = 1;
        [self connectWithPublisherToken];
    }
}

- (void) initOutGoingCall
{
    NSMutableDictionary * inputDict = [NSMutableDictionary dictionary];
    [inputDict setObject:[ParseHelper loggedInUser].objectId forKey:@"callerID"];
    [inputDict setObject:appDelegate.userTitle forKey:@"callerTitle"];
    [inputDict setObject:self.callReceiverID forKey:@"receiverID"];
    [inputDict setObject:[NSNumber numberWithBool:self.bAudio] forKey:@"isAudio"];
    [inputDict setObject:[NSNumber numberWithBool:self.bVideo] forKey:@"isVideo"];
    m_connectionAttempts = 1;
    [ParseHelper saveSessionToParse:inputDict];
}

As a matter of its duty, LSStreamingViewController handles both outgoing and incoming calls. To differentiate the two, it uses receiver ID (self.callreceiverID): For outgoing calls, it has a value supplied from LSViewController (see the segue transition code). For incoming calls, there is no need for it so it is null or empty.

As soon as saveSessionToParse saves ActiveSessions object to Parse.com database, it notifies LSStreamingViewController so that sessionID, publisherToken and subscriberToken values from Opentok (that became available to app’s delegate) can be usable to LSStreamingViewController. This notification (kSessionSavedNotification) is handled by sessionSaved like this:

//LSStreamingViewController.m
- (void) sessionSaved
{
    [self connectWithSubscriberToken];
}

In forthcoming section we will see how the above call makes video chat fully seamless between two users, without Parse intervention.

Huh..the mammoth has been laid to rest, but there is still life in it. We already covered session generation part. But how does the other user know about it? And when exactly Opentok takes the charge to start the exciting video?

Step 2 – Handle Incoming Call:

Handling of an incoming call is tricky bit. Let’s list out the bare minimum necessities:

  • You need to poll the database for a session destined to you (logged on user) – that is – search for an ActiveSessions record where current user is listed as receiver.
  • You need to ensure that database is up-to-date once the call has been established – that is, remove the session row once sessionID and tokens have been read up into iPhone app
  • You also need to signal interruptions while a session is ON – that is, inform the caller gracefully that the receiver is busy on another call. For simplicity’s sake, we aren’t handling multi-user calls (conference) right now, although it can be handled quite easily.

Recall that in alertView:(UIAlertView *)alertView clickedButtonAtIndex, we saw a call to [appDelegate fireListeningTimer],and now it is time to expand it, because it accomplishes our first task of the three listed above: It fires a timer that continually polls Parse.com ActiveSessions table for calls destined to current user.

//LSAppDelegate.m
//this method will be called once logged in. It will poll parse ActiveSessions object
//for incoming calls.
-(void) fireListeningTimer
{
    if (self.appTimer && [self.appTimer isValid])
        return;

    self.appTimer = [NSTimer scheduledTimerWithTimeInterval:8.0
                                                     target:self
                                                   selector:@selector(onTick:)
                                                   userInfo:nil
                                                    repeats:YES];
    [ParseHelper setPollingTimer:YES];  
    NSLog(@"fired timer");
}

-(void)onTick:(NSTimer *)timer
{
    NSLog(@"OnTick");
    [ParseHelper pollParseForActiveSessions];  
}

As it is named, [ParseHelper pollParseForActiveSessions] will poll ActiveSessions table for sessions calling out to this user – that is, rows which have receiverID = currently logged on user’s object ID.

 //ParseHelper.m
 //poll parse ActiveSessions object for incoming calls.
 +(void) pollParseForActiveSessions
 {
     __block PFObject *activeSession;

     if (!bPollingTimerOn)
         return;

     PFQuery *query = [PFQuery queryWithClassName:@"ActiveSessions"];

     NSString* currentUserID = [self loggedInUser].objectId;
     [query whereKey:@"receiverID" equalTo:currentUserID];  

     [query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error)
      {
          if (!error)
          {
              // if user is active user already, just update the entry
              // otherwise create it.
              LSAppDelegate * appDelegate = [[UIApplication sharedApplication] delegate];

              if (objects.count == 0)
              {

              }
              else
              {
                  activeSession = (PFObject *)[objects objectAtIndex:0];                 
                  appDelegate.sessionID = activeSession[@"sessionID"];
                  appDelegate.subscriberToken = activeSession[@"subscriberToken"];
                  appDelegate.publisherToken = activeSession[@"publisherToken"];
                  appDelegate.callerTitle = activeSession[@"callerTitle"];
                 // future use:
                  //appDelegate.bAudioCallOnly = !([activeSession[@"isVideo"] boolValue]);

                  //done with backend object, remove it.
                  [self setPollingTimer:NO];
                  [self deleteActiveSession];

                  NSString *msg = [NSString stringWithFormat:@"Incoming Call from %@, Accept?", appDelegate.callerTitle];                  
                  UIAlertView *incomingCallAlert = [[UIAlertView alloc] initWithTitle:@"LiveSessions" message:msg delegate:self cancelButtonTitle:@"No" otherButtonTitles:@"Yes", nil];                 
                  incomingCallAlert.tag = kUIAlertViewTagIncomingCall;
                  [incomingCallAlert show];                 
              }
          }
          else
          {
              NSString * msg = [NSString stringWithFormat:@"Failed to retrieve active session for incoming call. Please try again. %@", [error localizedDescription]];
              [self showAlert:msg];
          }
     }];
}

The method is quite self-explanatory – whenever it finds an ActiveSessions object, it just copies all the fields it needs – sessionID, publisherToken, and subscriberToken into app delegate’s properties. Once done, it deletes it from Parse.com backend using [self deleteActiveSession] call. [self setPollingTimer:NO] is to keep things in sync: it ensures that timer doesn’t fire up another polling query through pollParseForActiveSessions after an object has been found and deletion is in progress using [self deleteActiveSession].

Once the ActiveSession values are copied to App’s delegate, more important stuff is waiting: user needs to be notified of an incoming call. incomingCallAlert performs this task, and here is the result:

 iPhone video chat

What’s more important is incomingCallAlert’s delegate, which we already visited in Step 1 – let’s go over it again:

//ParseHelper.m
+ (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (kUIAlertViewTagUserName == alertView.tag)
    {
        //lets differ saving title till we have the location.
        //saveuserwithlocationtoparse will handle it.
        LSAppDelegate * appDelegate = [[UIApplication sharedApplication] delegate];
        appDelegate.userTitle = [[alertView textFieldAtIndex:0].text copy];
        appDelegate.bFullyLoggedIn = YES;

        //fire appdelegate timer
        [appDelegate fireListeningTimer];
        [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:kLoggedInNotification object:nil]];
    }
    else if (kUIAlertViewTagIncomingCall == alertView.tag)
    {
        if (buttonIndex != [alertView cancelButtonIndex])   //accept the call
        {
            //accept the call
            [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:kIncomingCallNotification object:nil]];
        }
        else
        {
            //user did not accept call, restart timer 
            //start polling for new call.
            [self setPollingTimer:YES];
        }
    }
}

If user had not accepted the call, the polling timer flag is set and app starts to look for new incoming call session. If user rather decides to accept the call,  kIncomingCallNotification is posted, and it is responsible for notifying LSViewController that a call has arrived. Rest of the stuff happens under LSViewController, which in turn hands it over to LSStreamingViewController.

//if and when a call arrives- 
(void) didCallArrive
{    
     //pass blank because call has arrived, no need for receiverID.
     m_receiverID = @"";
     [self goToStreamingVC];
}

didCallArrive fires in response to kIncomingCallNotification, and all it does it empty the m_receiverID to indicate that call is destined to self – an incoming call. This, as we already saw in prepareForSegue – is enough to signal LSStreamingViewController that call is supposed to be handled as incoming call – so no new ActiveSessions object need to be stored. All that is left is to utilize the sessionID and token values to connect to Tokbox streaming server.

So far, we discussed both cases – in both we obtained sessionID, publisherToken and subscriberToken into our app’s delegate. We finally passed the control over to LSStreamingViewController. In case of an outgoing call, [LSStreamingViewController sessionSaved] function calls [LSStreamingViewController connectWithSubscriberToken]. In case of incoming call, as we already saw in [LSStreamingViewController viewDidAppear], a call is made to [LSStreamingViewController connectWithPublisherToken].

//LSStreamingViewController.m
- (void) connectWithPublisherToken
{
    NSLog(@"connectWithPublisherToken");
    [self doConnect:appDelegate.publisherToken :appDelegate.sessionID];
}

- (void) connectWithSubscriberToken
{
    NSLog(@"connectWithSubscriberToken");    
    [self doConnect:appDelegate.subscriberToken :appDelegate.sessionID];
}

- (void)doConnect : (NSString *) token :(NSString *) sessionID
{
    _session = [[OTSession alloc] initWithSessionId:sessionID
                                           delegate:self];
    [_session addObserver:self forKeyPath:@"connectionCount"
                  options:NSKeyValueObservingOptionNew
                  context:nil];
    [_session connectWithApiKey:kApiKey token:token];
}

The only difference between two of them is the token they use – and Opentok isn’t quite clear about what changes if you use one token instead of the other (publisher token or subscriber token – as you remember it is generated from cloud code in beforeSave trigger). Irrespective of which one you use to connect to a session, it allows you to publish your stream (your camera feed) as well as subscribe to other user’s stream.

Once [_session connectWithApiKey] call is made, Opentok takes over.  If you recall the analogy, the teller counter finally puts the token guy out of the way, and start serving its customer. All you need to remember is that your streaming view controller (LSStreamingViewController) must implement these protocols: OTSessionDelegate, OTSubscriberDelegate, OTPublisherDelegate. See the activity flow diagram up again – there, iOS app initiated actions are listed in yellow, and delegates are marked in green. These delegate functions are part of these three protocols that LSStreamingViewController must implement. As they are called by Opentok along the flow, you need to take various actions to make that enticing video available to your user.

Now it no longer matters whether you are a caller or a receiver as far as you implement necessary delegate methods from Opentok. The Broadcast tutorial from Opentok has all implementation details, and I have followed it bit by bit, apart from my own UI modifications. For example, if you choose to view your own stream as soon as session gets connected, following code accomplishes it:

//LSStreamingViewController.m
- (void)sessionDidConnect:(OTSession*)session
{ 
    NSLog(@"sessionDidConnect: %@", session.sessionId);
    NSLog(@"- connectionId: %@", session.connection.connectionId);
    NSLog(@"- creationTime: %@", session.connection.creationTime);
    [self.disconnectButton setHidden:NO];
    [self.view bringSubviewToFront:self.disconnectButton];

    self._statusLabel.text = @"Connected, waiting for stream...";  
    [self.view bringSubviewToFront:self._statusLabel];

    [self doPublish];
}

- (void)doPublish
{
    _publisher = [[OTPublisher alloc] initWithDelegate:self name:UIDevice.currentDevice.name];
    _publisher.publishAudio = self.bAudio;
    _publisher.publishVideo = self.bVideo;
    [_session publish:_publisher];

    //symmetry is beauty.
    float x = 5.0;
    float y = 5.0;
    float publisherWidth = 120.0;
    float publisherHeight = 120.0;

    [_publisher.view setFrame:CGRectMake(x,y,publisherWidth,publisherHeight)];
    [self.view addSubview:_publisher.view];
    [self.view bringSubviewToFront:self.disconnectButton];
    [self.view bringSubviewToFront:self._statusLabel];

    NSLog(@"%f-%f-%f-%f", _publisher.view.frame.origin.x, _publisher.view.frame.origin.y, _publisher.view.frame.size.width, _publisher.view.frame.size.height);

    _publisher.view.layer.cornerRadius = 10.0;
    _publisher.view.layer.masksToBounds = YES;
    _publisher.view.layer.borderWidth = 5.0;
    _publisher.view.layer.borderColor = [UIColor yellowColor].CGColor;
}

In the code above, [_session publish] call prompts the user to allow his / her own camera feed, and as soon as he / she allows it, LiveSessions start publishing the camera feed to Opentok streaming server. A crucial piece to remember here is the call to following:

_publisher.view setFrame

SDK is designed such that without this, you never get to see your own feed. And no, any indirect method (e.g. addSubView to a container view) to set the frame doesn’t work. At the same time you can decorate your publisher view. For example, we have changed features like border color and corner radius.

Seeing the feed of the other user in the same session is somewhat that doesn’t fall into any order. All you need to do is implement necessary delegates so that as soon as you start receiving that feed, you get an opportunity to configure it fully – like this:

//LSStreamingViewController.m
- (void)subscriberDidConnectToStream:(OTSubscriber*)subscriber
{
    NSLog(@"subscriberDidConnectToStream (%@)", subscriber.stream.connection.connectionId);

    float subscriberWidth = [[UIScreen mainScreen] bounds].size.width;
    float subscriberHeight = [[UIScreen mainScreen] bounds].size.height - self.navigationController.navigationBar.frame.size.height;

    NSLog(@"screenheight %f", [[UIScreen mainScreen] bounds].size.height);
    NSLog(@"navheight %f", self.navigationController.navigationBar.frame.size.height);

    //fill up entire screen except navbar.
    [subscriber.view setFrame:CGRectMake(0, 0, subscriberWidth, subscriberHeight)];

    [self.view addSubview:subscriber.view];
    self.disconnectButton.hidden = NO;

    if (_publisher)
    {
        [self.view bringSubviewToFront:_publisher.view];
        [self.view bringSubviewToFront:self.disconnectButton];
        [self.view bringSubviewToFront:self._statusLabel];
    }
    subscriber.view.layer.cornerRadius = 10.0;
    subscriber.view.layer.masksToBounds = YES;
    subscriber.view.layer.borderWidth = 5.0;
    subscriber.view.layer.borderColor = [UIColor lightGrayColor].CGColor;

    self._statusLabel.text = @"Connected and streaming...";
    [self.view bringSubviewToFront:self._statusLabel];
}

subscriberDidConnectToStream delegate allows you to configure your own subscriber view. Again, setFrame statement is crucial and if you don’t include it or do it with wrong values, you may never get to see other user’s feed – something that can break your (and your users’) heart! Again, you can do your own UI modifications such as reporting the current status (using _statusLabel) and decorating the subscriber view inside the same delegate.

There are plenty of other delegates that Opentok sdk provides that you can use to include various features to smarten your app. For example, see how I chose to handle subscriber didFailWithError delegate:

//LSStreamingViewController.m
- (void)subscriber:(OTSubscriber *)subscriber didFailWithError:(OTError *)error
{
    NSLog(@"subscriber: %@ didFailWithError: ", subscriber.stream.streamId);
    NSLog(@"- code: %d", error.code);
    NSLog(@"- description: %@", error.localizedDescription);
    self._statusLabel.text = @"Error receiving video feed, disconnecting...";
    [self.view bringSubviewToFront:self._statusLabel];
    [self performSelector:@selector(doneStreaming:) withObject:nil afterDelay:5.0];
}

- (IBAction)doneStreaming:(id)sender
{
    [self disConnectAndGoBack];
}

- (void) disConnectAndGoBack
{
    [self doUnpublish];
    [self doDisconnect];
    self.disconnectButton.hidden = YES;
    [ParseHelper deleteActiveSession];

    //set the polling on.
    [ParseHelper setPollingTimer:YES];
    [self dismissModalViewControllerAnimated:YES];
}

There is much more inside LSStreamingViewController that needs little or no explanation for someone who knows UIKit well. So we proudly declare that the mammoth may have stopped breathing -you can see it yourself:

iPhone video chat

By the way, who is that insane soul screaming in the publisher view? LiveSessions isn’t that smart – what it shows there is what your iPhone’s camera sees!

And yeah, if you didn’t notice, the sleeping beast is an elephant, not a mammoth. Mammoths existed only in ice age. So is this real?

Who knows, it’s just virtual. But so are nerds trolling in chat rooms.

All we need now is to discard the remnants to smoother the flow of our iPhone video chat – let’s do them in one go.

The Cleanup:

As a VOIP app, LiveSessions must either keep running in the background or do its part of cleanup as soon as it enters background. To preserve simplicity, I chose later. There are some rules laid by Apple to perform any task in background – be it trivial or not. Within LSAppDelegate.m, we cleanup our back end following those rules.

- (void)applicationDidEnterBackground:(UIApplication *)application
{    
    backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^{

        // Clean up any unfinished task business by marking where you        
        // stopped or ending the task outright.        
        [application endBackgroundTask:backgroundTask];        
        backgroundTask = UIBackgroundTaskInvalid;        
    }];

    // Start the long-running task and return immediately.    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
    ^{
        // Do the work associated with the task, preferably in chunks.
        [ParseHelper deleteActiveSession];
        [ParseHelper deleteActiveUser];
        [application endBackgroundTask:backgroundTask];        
        backgroundTask = UIBackgroundTaskInvalid;        
    });    
}

And we also wake up following those rules:

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
    self.bFullyLoggedIn = NO;
    [ParseHelper initData];
    [ParseHelper anonymousLogin];    
}

And here goes what we call in the above code:

+ (void) deleteActiveSession
{
    NSLog(@"deleteActiveSession");
    LSAppDelegate * appDelegate = [[UIApplication sharedApplication] delegate];
    NSString * activeSessionID = appDelegate.sessionID;

    if (!activeSessionID || [activeSessionID isEqualToString:@""])
        return;

    PFQuery *query = [PFQuery queryWithClassName:@"ActiveSessions"];
    [query whereKey:@"sessionID" equalTo:appDelegate.sessionID];

    [query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error)
    {
        if (!object)
        {
            NSLog(@"No session exists.");     
        }
        else
        {
            // The find succeeded.
            NSLog(@"Successfully retrieved the object.");
            [object deleteInBackgroundWithBlock:^(BOOL succeeded, NSError *error)
            {
                if (succeeded && !error)
                {
                    NSLog(@"Session deleted from parse");                   
                }
                else
                {
                    //[self showAlert:[error description]];
                    NSLog(@"%@", [error description]);
                }
            }];
        }
    }];
}

+ (void) deleteActiveUser
{
    NSString * activeUserobjID = [self activeUserObjectID];
    if (!activeUserobjID || [activeUserobjID isEqualToString:@""])
        return;

    PFQuery *query = [PFQuery queryWithClassName:@"ActiveUsers"];
    [query whereKey:@"userID" equalTo:activeUserobjID];

    [query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error)
    {
        if (!object)
        {
            NSLog(@"No such users exists.");
        }
        else
        {
            // The find succeeded.
            NSLog(@"Successfully retrieved the ActiveUser.");
            [object deleteInBackgroundWithBlock:^(BOOL succeeded, NSError *error)
             {
                 if (succeeded && !error)
                 {
                     NSLog(@"User deleted from parse");
                     activeUserObjectID = nil;
                 }
                 else
                 {
                     //[self showAlert:[error description]];
                      NSLog(@"%@", [error description]);
                 }
             }];
        }
    }];
}

+(void) initData
{
    if (!objectsUnderDeletionQueue)
        objectsUnderDeletionQueue = [NSMutableArray array];
}

+ (bool) isUnderDeletion : (id) argObjectID
{
    return [objectsUnderDeletionQueue containsObject:argObjectID];
}

Both delete functions do what is expected – they delete ActiveUsers and ActiveSessions object from Parse.com database. There is nothing unusual that they do. objectsUnderDeletion array is our way of keeping things in sync: when Parse.com is busy deleting stuff in background, it prevents our app from repeatedly firing delete commands.

Sign off and Giving Ins:

This tutorial and the code that comes with can serve as bare backbone to your next cutting edge messenger App. You can do your own customizations for Parse user management or UI layout, and you can use it freely to learn, teach or sell – with only obligation of citing it back to myself, which you must do, and which will make me extremely happy.

Keep chatting…

General Knowledge Trivia

NTQZ – the novel concept education app for general knowledge trivia – has been upgraded on app store!

General Knowledge Trivia:

There are number of popular quiz apps for children, available on the web as well as on the app stores. Usually, any General Knowledge Trivia quiz contains questions aimed at revealing a lesser known fact about something very widely known.

Example: What color suit Bill Gates was wearing while presenting the first Windows OS version?

However, majority followers of General knowledge trivia find this boring – the content can at most, attract the creed of journalists, party mongers and page 3 enthusiasts. Such details require too large an attention span even over regular media.

Some other quizzes, in this sense, are reverse in nature technically. Questions revolve around lesser known trivia, leading to better known stuff. Like:

Question: National Socialist German Worker’s Party is shortly known as:

Answer: Nazi

Such quizzes are still general knowledge trivia, but are known as reverse trivia. The reversal is inserted in the kind of questions and answers. Questions look silly, answers the most authentic, sincere, great stuff. It can be a celebrity, a revolutionary event, or the greatest place to be.

The advantage of such reverse general knowledge trivia is, player is braced up for the element of surprise while answering the question – always wondering what is that big thing that’s hiding behind such innocent looking collection of phrases?

There is one downside though: The triviality of the question can render the player completely clueless, and hence bored. If the player is completely unaware of undercurrents during Nazi Party formation, he/she maybe unable to even wonder what the answer will be.

NTQZ – General Knowledge Trivia – of what kind?

NTQZ iPhone app has introduced yet another level of reversal in general knowledge trivia quizzes world. Here, instead of true answers, players are expected to come up with wrong answer.  And this single most factor opens infinite number of possibilities for players – especially children. Suddenly there is room to be extra-ordinarily funny.

The intent still remains sincere – like in any serious general knowledge trivia: you get to know the true fact. But unlike other quizzes where you get bored if you don’t know the answer, you still answer the question with brimming confidence – because NTQZ is there to certify your wits based on your answers!

Are these all features of a general knowledge trivia?

  • Like any other quiz app, it contains challenging questions aimed at boosting your child’s IQ - the focus is on General Knowledge trivia.
  • The twist in this quiz app is, funny answers are rewarded with positive points, while incorrect ones gets punished with negative points!
  • This quiz app questions range from many disciplines. They are capable of preparing your school child win QuizBowlsBrain BowlsOlympiadsWould Quizzing Champianship, and many more alike.
  • Quiz answers are not only funny but witty that can make him/her stand out of the group.
  • Contextual wikipedia help assists in grasping General knowledge trivia facts around the question.
  • Last but not the least: NTQZ iphone quiz app got a new icon that’s better representative of it’s theme.
  • Read more about NTQZ features here.

 

Quiz for children: Everything can be taught – except the discretion (of what’s worth learning)

Life seems to be a choice between two wrong answers.
– Sharyn McCrumb

quiz for children

Do you know what am I going to talk about quiz for children? You are wrong! Read on…!

What? You don’t know? Can’t grasp the initial quote? Read on…!

(Choice between two wrong answers can’t be worse than this :-()

A Diversion:

In fact, this article is not a quiz for children. It’s quiz for parents – you. Tomorrow’s world would require your kids not only to be capable to earn their bread, but also to sustain and grow themselves in competition.

They not only need to be extra-capable, extra-efficient, but also super-knowledgeable and ingeniously creative.

They need to learn things different way than you did.

It’s great to buy amazing cloths for your kids.
But as time flies, you will realize it’s also important for them to learn what’s worth wearing.

It’s great to make them great recipes when they come back home.
But as they grow, it’s also important to make them realize what should be eaten at what times. And off course, what should be avoided.

This was to be for Quiz for Children – what does all this mean, after-all?

Parental discretion can greatly help up to certain age, but that’s never guaranteed to sustain. It cripples their transition to maturity.

Blast of information and networking ensures that your kids ought to be lot more independent than you once thought. And they need to self-dependent far earlier than you yourself became.

Like food, fashion and career, and more than all of them, learning requires discretion.

It’s true that everything can be taught.
But what’s worth learning can’t be!

So what are you getting at, finally?

Your kids not only need to know and memorize the facts.
They also need extra-super judgement about what’s worth their attention.

They need to observe, keenly observe, and grasp useful things fast.
They need to distract from trash even faster.
They must know what to avoid, and how quickly.

They not only need to know what’s correct answer to a given question, out of choices given to them.
They also need to know why it’s single most correct answer.
They also need to know why all other answers are wrong - to make that single answer the most correct.

They also need to know what’s worse than being right, and what’s worst - the wrongest!
In a life when you simply can’t resist what’s worst, you can at least choose how to react to it.

(Now you know why I quoted Sharyn McCrumb in the beginning…)

Being able to choose a wrong answer is a special ability that everyone possesses. But it must be cultivated. Your kids must decide whether to hate or laugh at the wrongest. And if they are trained at it, they can easily do so.

Not convinced?
The real magnetic test of an iron rod A is not whether it can attract another iron rod B.  Come on, even B could be a magnet.
The real test would be, whether A repels B or not. Only a magnet can repel another magnet, not an iron rod.

Repulsion is the true test of magnetism, not attraction.

Likewise, incorrect answers are correct proof of knowledge, not the correct ones! That’s why, NTQZ punishes the player for the right answer, and rewards him/her for the wrongest of all.

NTQZ  (aNTiQuiZ) – iPhoneGameZone’s newest iPhone quiz for children, is an educational product aimed at raising your child’s wisdom bar. It incites your child with questions that surround amazing facts that just wait to be digested. The carefully chosen trivia questions simply open the door to so much content that your kids are hungrier after answering them. They just can’t stop reading encyclopedia articles surrounding it.

What’s more, they are encouraged to be extremely creative (read wrong) about each of them, and are rewarded for them. The experience enhances their analytical strength and creativity. Leisure of being wrong is a fountainhead of creativity.

As a result, after taking NTQZ, your kids are not only knowledgeable, but also are wiser, wittier and funny.

With knowledge, they can’t lose a competition – that’s better than anything.

With wisdom, even if they lose, they don’t lose in life.

And what’s better than that? Huh?

Image courtesy of Stuart Miles at FreeDigitalPhotos.net

Ever seen it? Text Field in Navigation bar…

In this new category of posts, we will include tiny tips and tricks that makes you pull your hair in the dark of the night!

Here is first one.

Very often, you are challenged with weird UI requirements – putting an interactive control under Navigation bar is just one of those headaches. It is very easy to forget the basics when things get mixed up. Here is step by step:

  • Create your XCode app just like 1-2-3. The kind of app that I have chosen to demonstrate is a master-detail app.
  • In master view controller, add following piece of code:
- (void) addTextField
{
    CGRect textFieldFrame = CGRectMake(20.0f, 100.0f, 280.0f, 31.0f);
    UITextField *textField = [[UITextField alloc] initWithFrame
    :textFieldFrame];

    textField.placeholder = @"Enter Text!";
    textField.backgroundColor = [UIColor whiteColor];
    textField.textColor = [UIColor blackColor];

    textField.borderStyle = UITextBorderStyleRoundedRect;
    textField.clearButtonMode = UITextFieldViewModeWhileEditing;
    textField.returnKeyType = UIReturnKeyDone;

    textField.delegate = self; // ADD THIS LINE
    self.navigationItem.rightBarButtonItem = 
    [[UIBarButtonItem alloc] initWithCustomView:textField];
}
  • Now, in your master view controller .h file, in front of the view controller definition, add this:
@interface TabAppMasterViewController : 
UITableViewController <UITextFieldDelegate>
  • Finally, add following delegate functions to your master view controller .m file as is – I haven’t added any implementation to them. They are just for you to test whether they are hit or not. This will be your final test to check if you successfully added text field:
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
    NSLog(@"begin!");
}

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    NSLog(@"end!");
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
     [textField resignFirstResponder];
    return YES;
}
  • After doing everything, just build, sit back, and watch it come alive:

 

 

PS: Hate patchwork? Here is the entire package,