Skip to content
This repository has been archived by the owner on Apr 27, 2021. It is now read-only.

Commit

Permalink
UX improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Cloud Solutions Team committed Nov 23, 2013
1 parent f859010 commit 570f1ca
Show file tree
Hide file tree
Showing 66 changed files with 2,682 additions and 1,042 deletions.
1,227 changes: 656 additions & 571 deletions CloudBackendIOSClient.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 2 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Unless required by applicable law or agreed to in writing, software distributed
This sample application is not an official Google product.

## Support Platform and Versions
This sample source code and project is designed to work with XCode 4.6. The resulted application is tested on iOS 6.1 and iPhone 5.
This sample source code and project is designed to work with XCode 4.6. The resulted application has been tested on iOS 6.1 and iPhone 5.

## Overview
This iOS client is designed to work with Mobile Backend Starter backend.
Expand All @@ -22,56 +22,4 @@ This iOS client is designed to work with Mobile Backend Starter backend.
Download this sample code from [Google Cloud Platform Github](https://github.com/GoogleCloudPlatform/solutions-mobile-backend-starter-ios-client).

## Developer Guide
This section provides a step-by-step guide so you can get the sample up and running in Xcode.

### Prerequisite
1. Download and install [Xcode 4.6](https://developer.apple.com/xcode/) on your Mac computer if you don't have it installed.

2. Download the Mobile Backend Starter [backend](https://github.com/GoogleCloudPlatform/solutions-mobile-backend-starter-java).

3. You have a valid SSL certiciate that is APNS enabled (for the Java backend), a corresponding Provisioning Profile installed on your Mac computer. Otherwise, please follow the [Apple documentation](http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ProvisioningDevelopment.html#//apple_ref/doc/uid/TP40008194-CH104-SW1) to obtain them.

If you do have a push notification certificate installed prior on your machine. You can follow these steps to export the \*.p12 file for step 5 below:
* Open KeyChain Access
* Browse `My Certificates` under category on the left
* Expand the `push certificate`
* Select both the push certificate and key
* Right click and choose to export 2 items
* Set a password on the certificate

4. Follow the Mobile Backend Starter README.md and deploy the Mobile Backend Starter to App Engine.

5. Navigate to the Mobile Backend Starter configuration page i.e. *https://your_app_id.appspot.com/admin/configure.jsp* to register iOS Client ID by setting the Authentication/Authorization option to "Secured by Client IDs". Take a note of this Client ID which will be reentered on the iOS client side. Next, enable *Google Cloud Messaging and iOS Push Notification* and provide the `APNS Provider Certification Password`. At last, click *Select APNS Certificate and Save* to upload the \*.p12 certificate and finally save the configuration.

6. You have an iPhone, that is provisioned for development and runs iOS version 6.1.

### Set up Xcode Project

#### Open CloudBackendIOSClient.xcodeproj in Xcode
1. Open a new Finder window and navigate to the directory you extract the sample client code. Double click on the ClientBackendIOSClient.xcodeproj file. It will open the project in Xcode automatically.

#### Rename the bundle ID
1. The iOS client application bundle ID has to match the one you used for creating the SSL certificate and the Provisioning Profile. Out-of-the-box bundle ID is `com.google.CloudPushSample.dev`. Please rename the bundle ID accordingly via project TARGETS in Xcode.

#### Update the Client ID, Client Secret and Service URL
1. Fill in the kCloudBackendClientID and kCloudBackendClientSecret values in Constants.m. The kCloudBackendClientID has to match with the Client ID you used in the backend as described in step 5 of the Prerequisite. The kCloudBackendClientSecret is the matching client secret for the Client ID from [API Console](https://code.google.com/apis/console).
2. Replace *{{{ INSERT APP ID }}}* in the kCloudBackendServiceURL variable in Constants.m with the App Engine Application id where the Mobile Backend Starter is deployed to.

#### Update the Code Signing Certificate
1. Click on the project in the Ffile Browser panel
2. Click on the project in the Settings browser
3. Click on the Build Settings tab
4. Browse down to Code Signing Identity
5. Select a valid code signing certificate (APNS enabled provisioning profile)

### Build and Execute Mobile Backend Starter iOS Client
1. On the top left corner of the toolbar, select `[Your bundle ID] > iOS Device`. Then click the `Run` button to execute the application.
2. The application should open up in your iPhone.

### Testing the Backend and iOS Client Altogether
1. If this is the first time the CloudBackendIOSClient app executes, a Google login page may show up asking for your credential and your consent to identify your account. Go ahead and sign in.
2. Click on the the "+" sign on the top right corner and enter a message. Hit *return* will save the input.
3. The Guestbook should refresh and show the latest message. You can also use the pull down gesture to refresh the list manually. Note that each time when there is a new message or update sent to the backend, this application will be notified by push notification which triggers this application to query against the backend. You can test this by entering a new message and immediately hitting the *home* key to navigate away from this application. Within seconds, you will receive a push notification alerting you that a message is received. When you navigate back to the application, you will see the latest message being queried and displayed.
4. Repeat step 2 a few times. You should see a list of messages displayed in reverse chronological order.
5. If you have a few Android or iOS devices, register them to the same Mobile Backend Starter backend, you can then use this application to communicate among multiple devices.
6. Open any browser and navigate to the [Mobile Backend Starter configuration page](https://your\_app\_id.appspot.com/admin/configure.jsp). In the *Send Cloud Message* section, click *Send*. Your phone will then receive a push notification which alerts the Guestbook to display an alert. This alert will display the broadcast message as specified and dismiss automatically based on the duration value.
To run this sample follow the steps described in the [documentation](https://developers.google.com/cloud/samples/mbs/ios/)
2 changes: 1 addition & 1 deletion api/CloudEntity.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,4 @@ extern NSString *const kCloudEntityScopeFutureAndPast;
- (void)removeInstanceAtIndexPath:(NSIndexPath *)indexPath
callback:(CloudEntityQueryCompletionCallback)block;

@end
@end
32 changes: 14 additions & 18 deletions api/CloudFilter.m
Original file line number Diff line number Diff line change
Expand Up @@ -107,19 +107,14 @@ + (CloudFilter *)cloudFilterNe:(NSString *)property value:(id)value {
+ (CloudFilter *)cloudFilterInValues:(NSString *)property
values:(id)value, ... {
CloudFilter *f = [[CloudFilter alloc] init];
f.filterDto = [[GTLMobilebackendFilterDto alloc] init];
f.filterDto.operatorProperty = @"IN";

if (property && value) {
if (value) {
NSArray *values = [NSMutableArray arrayWithObject:value];
va_list argumentList;
va_start(argumentList, value);

NSMutableArray *array = [NSMutableArray arrayWithObject:property];
[array addObjectsFromArray:[self arrayFromAugumentList:value
arguments:argumentList]];
f.filterDto.values = array;
values = [self arrayFromAugumentList:value arguments:argumentList];
va_end(argumentList);

return f;
}

Expand All @@ -144,7 +139,7 @@ + (CloudFilter *)cloudFilterOrFilters:(CloudFilter *)filter, ... {
NSArray *subfilters = [self filterDtoArrayFromArgumentList:filter
arguments:argumentList];
va_end(argumentList);

return [CloudFilter filterAndOrWithOperator:FilterOperatorOR
filters:subfilters];
}
Expand All @@ -153,10 +148,12 @@ + (CloudFilter *)cloudFilterOrFilters:(CloudFilter *)filter, ... {
+ (NSArray *)arrayFromAugumentList:(id)firstObject
arguments:(va_list)argumentList {
NSMutableArray *resultedArray = [[NSMutableArray alloc] init];
id eachItem = firstObject;
while (eachItem) {
[resultedArray addObject:firstObject];

// Loop the rest
id eachItem = nil;
while ((eachItem = va_arg(argumentList, id))) {
[resultedArray addObject:eachItem];
eachItem = va_arg(argumentList, id);
}

return resultedArray;
Expand All @@ -166,13 +163,12 @@ + (NSArray *)arrayFromAugumentList:(id)firstObject
+ (NSArray *)filterDtoArrayFromArgumentList:(CloudFilter *)firstObject
arguments:(va_list)argumentList {
NSMutableArray *resultedArray = [[NSMutableArray alloc] init];
[resultedArray addObject:firstObject.filterDto];

CloudFilter *eachItem = firstObject;
while (eachItem) {
if (eachItem.filterDto) {
[resultedArray addObject:eachItem.filterDto];
}
eachItem = va_arg(argumentList, CloudFilter *);
// Loop the rest
CloudFilter *eachItem = nil;
while ((eachItem = va_arg(argumentList, CloudFilter *))) {
[resultedArray addObject:eachItem.filterDto];
}

return resultedArray;
Expand Down
Binary file added art/ic_launcher_guestbook.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added art/ic_left_arrow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added art/ic_menu_guestbook.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added art/ic_right_arrow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added art/ic_send.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added art/img_connect.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added art/img_done.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added art/img_dots_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added art/img_dots_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added art/img_dots_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added art/img_guestbook_splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added art/img_logo_splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 6 additions & 4 deletions gtl/GTLJSONParser.m
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ + (void)load {
}
#endif // DEBUG && !GTL_REQUIRES_NSJSONSERIALIZATION

+ (NSString*)stringWithObject:(id)obj
humanReadable:(BOOL)humanReadable
error:(NSError**)error {
+ (NSString *)stringWithObject:(id)obj
humanReadable:(BOOL)humanReadable
error:(NSError **)error {
NSData *data = [self dataWithObject:obj
humanReadable:humanReadable
error:error];
Expand All @@ -73,7 +73,9 @@ + (NSString*)stringWithObject:(id)obj

+ (NSData *)dataWithObject:(id)obj
humanReadable:(BOOL)humanReadable
error:(NSError**)error {
error:(NSError **)error {
if (obj == nil) return nil;

const NSUInteger kOpts = humanReadable ? (1UL << 0) : 0; // NSJSONWritingPrettyPrinted

#if GTL_REQUIRES_NSJSONSERIALIZATION
Expand Down
10 changes: 0 additions & 10 deletions gtl/GTLObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,6 @@
#import "GTLUtilities.h"
#import "GTLDateTime.h"

#undef _EXTERN
#undef _INITIALIZE_AS
#ifdef GTLOBJECT_DEFINE_GLOBALS
#define _EXTERN
#define _INITIALIZE_AS(x) =x
#else
#define _EXTERN extern
#define _INITIALIZE_AS(x)
#endif

@protocol GTLCollectionProtocol
@optional
@property (retain) NSArray *items;
Expand Down
11 changes: 7 additions & 4 deletions gtl/GTLObject.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
// GTLObject.m
//

#define GTLOBJECT_DEFINE_GLOBALS 1

#include <objc/runtime.h>

#import "GTLObject.h"
Expand Down Expand Up @@ -516,8 +514,13 @@ + (GTLObject *)objectForJSON:(NSMutableDictionary *)json
defaultClass:(Class)defaultClass
surrogates:(NSDictionary *)surrogates
batchClassMap:(NSDictionary *)batchClassMap {
if ([json count] == 0 || [json isEqual:[NSNull null]]) {
// no actual result, such as the response from a delete
if ([json isEqual:[NSNull null]] || [json count] == 0) {
if (json != nil && defaultClass != Nil) {
// The JSON included an empty dictionary, and a return class
// was specified.
return [defaultClass object];
}
// No actual result, such as the response from a delete.
return nil;
}

Expand Down
5 changes: 5 additions & 0 deletions gtl/GTLQuery.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,8 @@
+ (NSDictionary *)parameterNameMap;
+ (NSDictionary *)arrayPropertyToClassMap;
@end

// The library doesn't use GTLQueryCollectionImpl, but it provides a concrete implementation
// of the protocol so the methods do not cause a private method error in Xcode.
@interface GTLQueryCollectionImpl : GTLQuery <GTLQueryCollectionProtocol>
@end
20 changes: 12 additions & 8 deletions gtl/GTLQuery.m
Original file line number Diff line number Diff line change
Expand Up @@ -213,17 +213,17 @@ + (NSDictionary *)arrayPropertyToClassMap {

#pragma mark Runtime Utilities

static NSMutableDictionary *gParameterNameMapCache = nil;
static NSMutableDictionary *gArrayPropertyToClassMapCache = nil;
static NSMutableDictionary *gQueryParameterNameMapCache = nil;
static NSMutableDictionary *gQueryArrayPropertyToClassMapCache = nil;

+ (void)initialize {
// note that initialize is guaranteed by the runtime to be called in a
// thread-safe manner
if (gParameterNameMapCache == nil) {
gParameterNameMapCache = [GTLUtilities newStaticDictionary];
if (gQueryParameterNameMapCache == nil) {
gQueryParameterNameMapCache = [GTLUtilities newStaticDictionary];
}
if (gArrayPropertyToClassMapCache == nil) {
gArrayPropertyToClassMapCache = [GTLUtilities newStaticDictionary];
if (gQueryArrayPropertyToClassMapCache == nil) {
gQueryArrayPropertyToClassMapCache = [GTLUtilities newStaticDictionary];
}
}

Expand All @@ -232,7 +232,7 @@ + (NSDictionary *)propertyToJSONKeyMapForClass:(Class<GTLRuntimeCommon>)aClass {
[GTLUtilities mergedClassDictionaryForSelector:@selector(parameterNameMap)
startClass:aClass
ancestorClass:[GTLQuery class]
cache:gParameterNameMapCache];
cache:gQueryParameterNameMapCache];
return resultMap;
}

Expand All @@ -241,7 +241,7 @@ + (NSDictionary *)arrayPropertyToClassMapForClass:(Class<GTLRuntimeCommon>)aClas
[GTLUtilities mergedClassDictionaryForSelector:@selector(arrayPropertyToClassMap)
startClass:aClass
ancestorClass:[GTLQuery class]
cache:gArrayPropertyToClassMapCache];
cache:gQueryArrayPropertyToClassMapCache];
return resultMap;
}

Expand All @@ -265,3 +265,7 @@ + (BOOL)resolveInstanceMethod:(SEL)sel {
}

@end

@implementation GTLQueryCollectionImpl
@dynamic pageToken, startIndex;
@end
70 changes: 57 additions & 13 deletions gtl/GTLRuntimeCommon.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
//

#include <objc/runtime.h>
#include <TargetConditionals.h>

#import "GTLRuntimeCommon.h"

Expand Down Expand Up @@ -805,7 +806,8 @@ static void DynamicNSObjectSetter(id<GTLRuntimeCommon> self, SEL sel, id obj) {
static objc_property_t PropertyForSel(Class<GTLRuntimeCommon> startClass,
SEL sel, BOOL isSetter,
Class<GTLRuntimeCommon> *outFoundClass) {
const char *baseName = sel_getName(sel);
const char *selName = sel_getName(sel);
const char *baseName = selName;
size_t baseNameLen = strlen(baseName);
if (isSetter) {
baseName += 3; // skip "set"
Expand All @@ -822,23 +824,52 @@ static objc_property_t PropertyForSel(Class<GTLRuntimeCommon> startClass,
objc_property_t *properties = class_copyPropertyList(currClass, NULL);
if (properties) {
for (objc_property_t *prop = properties; *prop != NULL; ++prop) {
const char *propName = property_getName(*prop);
size_t propNameLen = strlen(propName);
const char *propAttrs = property_getAttributes(*prop);
const char *dynamicMarker = strstr(propAttrs, ",D");
if (!dynamicMarker ||
(dynamicMarker[2] != 0 && dynamicMarker[2] != ',' )) {
// It isn't dynamic, skip it.
continue;
}

// search for an exact-name match (a getter), but case-insensitive on the
if (!isSetter) {
// See if this property has an explicit getter=. (the attributes always start with a T,
// so we can check for the leading ','.
const char *getterMarker = strstr(propAttrs, ",G");
if (getterMarker) {
const char *getterStart = getterMarker + 2;
const char *getterEnd = getterStart;
while ((*getterEnd != 0) && (*getterEnd != ',')) {
++getterEnd;
}
size_t getterLen = (size_t)(getterEnd - getterStart);
if ((strncmp(selName, getterStart, getterLen) == 0)
&& (selName[getterLen] == 0)) {
// return the actual property
foundProp = *prop;
// if requested, return the class containing the property
if (outFoundClass) *outFoundClass = currClass;
break;
}
} // if (getterMarker)
} // if (!isSetter)

// Search for an exact-name match (a getter), but case-insensitive on the
// first character (in case baseName comes from a setter)
const char *propName = property_getName(*prop);
size_t propNameLen = strlen(propName);
if (baseNameLen == propNameLen
&& strncasecmp(baseName, propName, 1) == 0
&& (baseNameLen <= 1
|| strncmp(baseName + 1, propName + 1, baseNameLen - 1) == 0)) {
// return the actual property name
foundProp = *prop;
// return the actual property
foundProp = *prop;

// if requested, return the class containing the property
if (outFoundClass) *outFoundClass = currClass;
break;
}
}
// if requested, return the class containing the property
if (outFoundClass) *outFoundClass = currClass;
break;
}
} // for (prop in properties)
free(properties);
}
if (foundProp) return foundProp;
Expand Down Expand Up @@ -881,7 +912,8 @@ static objc_property_t PropertyForSel(Class<GTLRuntimeCommon> startClass,
// T@"NSString",&,D,P
// Ti,D -- NSInteger on 32bit
// Tq,D -- NSInteger on 64bit, long long on 32bit & 64bit
// Tc,D -- BOOL comes as char
// TB,D -- BOOL comes as bool on 64bit iOS
// Tc,D -- BOOL comes as char otherwise
// T@"NSString",D
// T@"GTLLink",D
// T@"NSArray",D
Expand Down Expand Up @@ -932,13 +964,25 @@ static objc_property_t PropertyForSel(Class<GTLRuntimeCommon> startClass,
nil, nil,
NO
},
{ // BOOL
// This conditional matches the one in iPhoneOS.platform version of
// <objc/objc.h> that controls the definition of BOOL.
#if !defined(OBJC_HIDE_64) && TARGET_OS_IPHONE && __LP64__
{ // BOOL as bool
"TB",
"v@:B", (IMP)DynamicBooleanSetter,
"B@:", (IMP)DynamicBooleanGetter,
nil, nil,
NO
},
#else
{ // BOOL as char
"Tc",
"v@:c", (IMP)DynamicBooleanSetter,
"c@:", (IMP)DynamicBooleanGetter,
nil, nil,
NO
},
#endif
{ // NSString
"T@\"NSString\"",
"v@:@", (IMP)DynamicStringSetter,
Expand Down
Loading

0 comments on commit 570f1ca

Please sign in to comment.