Thursday, February 18, 2016

Facebook Audience Network Ad Approval

(Feb/2016)

I've been working on integrating Facebook Audience Network ads.    Before going "live", Facebook says that they have to approve the ad placement - they say this approval is initiated automatically when you've had 5 successful test ads and that the approval takes 2-3 business days.

In my case, I displayed some test ads on the simulator, and also on a device.  They tell you to use the API and register the device with a hash code they provide.

After almost a week, I hadn't heard anything from Facebook and was wondering what was going on.  After doing some online research, I found a discussion that suggested that the number of impressions was important.

I reran the app - this time with it running on a local device connected to Xcode.  On each refresh, it would write  [FBAudienceNetworkLog/FBAdUtility:396] Impression is logged (displayed for test ads only)  to the console. 

I let it go for a few minutes until there were at least five of these impressions.

Then, to my surprise, less than a minute or two later, I received a Facebook alert saying that the app had been approved.

I had a second app that I repeated the process with.  Again, after just a few minutes, the app was approved for ads.

*** Update 3/28 ***

Okay, after doing a few more apps, it seems that Facebook has changed the process such that they just automatically review apps after a few days.    I'm guessing that the number of impressions still matters, but it seems disassociated from when they actually review. 

Thursday, October 22, 2015

iOS 9 Issues

This is just a list for keeping track of iOS 9 related issues I've run into.


1) If you get a list of font families, some of those families do not contain any actual fonts - only seems to affect newer devices.  (reported to apple - marked as dupe)

2) Weird issues with UIView hit test not propagating.   Oddball things where it works everywhere but on an iPhone 6s.  Generally, once recompiled, it works fine.  Actually, I've found that recompiling or changing the hit test code does not work.  In fact, any old style touchesBegan type tap handling does not work on the iPhone 6s.

3) CSS Sibling selectors do not work in certain conditions, for example:

input:checked + label ~ div {
    z-index: 1;
}


changing to:

input:checked + label + div {
    z-index: 1;
}


does work, but doesn't work in iOS 8.   Adding both rules seems to do the trick.

4) Using willRotateToInterfaceOrientation to force a UISplitViewController to refresh the layout no longer works.  Should use  setPreferredDisplayMode instead, as in:

    [sv setPreferredDisplayMode:(hideMaster)?UISplitViewControllerDisplayModePrimaryHidden : UISplitViewControllerDisplayModeAllVisible];

for example.

5) External http calls not working (after iOS 9 recompile).  Need to add

NSAppTransportSecurity
   NSAllowsArbitraryLoads  : YES

to info plist.




Tuesday, October 6, 2015

How to use the UIFocusGuide for navigation

I really didn't quite understand how UIFocusGuides were supposed to work until after several hours of banging my head against the wall.  I think I got it now....

The focus guide, like the layout guide it extends, can be thought of as an invisible view - in this case, an invisible focusable view.

A UIFocusGuide functions as a bridge between different areas of the screen since focus navigation only works up/down or left/right.

Consider a screen that has two objects A and B.  

     AAAA

               BBBB

With the default focus behaviors, there is no way to transition from A to B because B is not directly to the right or bottom.

Thus, to make this work, you'd need a focus guide in between, like


     AAAA

FFFFFFFFFFFFFFFFFFFFF

               BBBB


and we would set the focus guide's preferredFocusedView = B to start.  

So, given this example layout, if view A has the focus, and the user navigates down, it will find the focus guide (F), and the focus guide will then redirect the focus to the guide's  preferredFocusedView, in this case B.

Of course, if B has the focus, you'd want to update the focus guide so its preferredFocusedView is now A, so that when the user navigates up, it goes from B to A.

Okay, now that you get the concept, what's the code look like?  Something like this (Warning: I have not compiled/run this segment):


UIFocusGuide *F;

- (void)viewDidLoad
{
   [super viewDidLoad];
   
   F = [UIFocusGuide new];
   [self.view addLayoutGuide:F];

   [F.topAnchor constraintEqualToAnchor:A.bottomAnchor].active = YES;
   [F.heightAnchor constraintEqualToConstant:1].active = YES;
   [F.leftAnchor constraintEqualToAnchor:self.view.leftAnchor].active = YES;
   [F.rightAnchor constraintEqualToAnchor:self.view.rightAnchor].active = YES;

   [F setPreferredFocusedView:B];
}
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
{

   if (context.nextFocusedView == A){
       [F setPreferredFocusedView:B];
   } else if (context.nextFocusedView == B){
       [F setPreferredFocusedView:A];
   }
}


Apple has a simple example in the UIKitCatalog demo that demonstrates the concept in swift.  The layout in that example case is more like:

AAAAA    BBBBB

CCCCC    FFFFF

and it shows the navigation between B and C via F.

Monday, August 31, 2015

Upload a file to Google Drive via PHP using REST

Disclaimer: I am (at best) a novice PHP coder.  There may well be better ways to accomplish this task, but this is what worked for me.

First off, Google does provide a nice-looking PHP library for working with Drive, but I was reluctant to introduce more libraries into our already complex and fragile server environment.

Using REST, Google's  documentation explains that there are three ways to upload files to Drive - using a media upload, a multipart upload, and a resumable upload. 

For our task, we were working with small spreadsheets so the resumable upload was overkill.

We really wanted to use multipart but gave up after many hours of trying but getting only 400 - Bad Request errors.  We tried using the exact type message that Google detailed in its documentation, but it still didn't work.   (Had I been a better PHP programmer, perhaps I could have figured out what was wrong).

Anyways, we moved on and used a combination of a media POST, to upload the file contents followed by a PATCH, to update the title.  Here's the code:


POSTing the file to upload.  (Assumes you have an authorization token).  The result of the file_get_contents call will be a JSON structure that represents a file resource.


$authstr = 'Bearer '.$token;

$fileToUpload = "datafiles/".$_FILES['uploadedFile']['name'].".csv";

$url = 'https://www.googleapis.com/upload/drive/v2/files?uploadType=media&convert=true'; 

$options = array(
        'http' => array(
            'header'  => "Content-type: application/vnd.ms-excel\r\nAuthorization: ".$authstr."\r\n",
            'method'  => 'POST',
            'content' => file_get_contents($fileToUpload),
        ),
 );
$context  = stream_context_create($options);
$result = file_get_contents($url, false, $context);

$json = json_decode($result);


PATCHing the file to change the title.  We use a different URL here and identify the file using the File Id returned from the media upload POST.  We tell it that we're passing a JSON structure and send along the attributes to change.


$url = 'https://www.googleapis.com/drive/v2/files/'.$json->id;
 

$options = array(
        'http' => array(
            'header'  => "Content-type: application/json\r\nAuthorization: ".$authstr."\r\n",
            'method'  => 'PATCH',
            'content' => '{"title" : "My New Filename"}',
        ),
 );

$context  = stream_context_create($options);

$result = file_get_contents($url, false, $context);
$json = json_decode($result);



Have a better solution?  I'd be interested to hear about it....




Thursday, May 28, 2015

Backing up Cordova databases, local storage, and CDVLocalStorage on iOS

On iOS, Cordova has a complex class, CDVLocalStorage which manages backup behavior via the BackupWebStorage flag.   It was a little hard to understand the gist of what was going on, so I'm describing the general process of how it works here.... Of course, please note that this information has a limited shelf life due to new releases of Cordova and iOS.

Another note is that if you're building with Xcode, the  BackupWebStorage flag is in the config.xml in the Staging area.

Anyways, there are three available values, none, local, and cloud.

When set to none, Cordova does very little and defers to the default iOS behavior which is to store local storage and WebSQL databases in Library/Caches.  This is an area which will not be backed up using iCloud. 

local has the most complex activity.  When set to local, Cordova adds a listener so that when the user exits the app, the databases and local storage will be copied to the Documents/backups directory.  This allows a user plugging their device into their computer and running iTunes backup to save those databases/local storage.  However, to prevent the iCloud backup from copying these files, the Documents/backups directory is marked with the "do not backup" flag.

When BackupWebStorage is set to cloud, the following is set:

[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"WebKitStoreWebDataForBackup"];

This flag causes iOS to move the databases/local storage files from Library/Caches to Library/WebKit.  Once there, the databases/local storage will be picked up by the iCloud backup.


CDVLocalStorage also creates various Cloud/NoCloud directories but it appears that none of these are ever used.

Thursday, December 4, 2014

A few more autolayout tips and tricks



1.  If you add an view to a container without specifying constraints to derive width/height  it will use the object's intrinsicContentSize.   This is nice if you want to maintain the aspect ratio of the view. 

2.  I have wasted many hours of my life that I'll never get back spent gobs of time attempting to mix IB Autolayout with programmatic constraints.  The problem is because IB automatically inserts its own constraints (thanks IB!) which then conflict with the ones that I'm trying to set (See #3 below).

Several StackOverflow posts recommend checking off the "Remove at build time" flag for constraints that should be set or adjusted programmatically.  That will work, but it means I'll always have to programmatically add that constraint - but sometimes the default IB constraint is fine until something else in the layout changes (e.g., a view is added or removed)

In that situation, just set the priority of the constraint in IB to "High" (vs "Required").  That way, if you add a constraint that conflicts with the default IB constraint, you won't get bad layouts or error messages.

3. Programmatically removing all of the IB built-in constraints for a given view seems to be beyond my technical abilities.  In some situations, no matter what I do, I just can't seem to get rid of those @#!$&$! default IB constraints.  See #2.

4. You can add the same constraint numerous times without error.  I'll leave it up to you to decide if that's a good thing

5. And so I don't forget,  here's how to position two views at 25% and 75% of parent width with programmatic constraints:

   [buttonPanel addConstraint:[NSLayoutConstraint constraintWithItem:button1 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:buttonPanel attribute:NSLayoutAttributeCenterX multiplier:.5 constant:0]];

   [buttonPanel addConstraint:[NSLayoutConstraint constraintWithItem:button2 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:buttonPanel attribute:NSLayoutAttributeCenterX multiplier:1.5 constant:0]];

Tuesday, September 9, 2014

Venting on the iOS8 iTunesConnect


I've had a few app updates in the queue for about 10 days now.  I put them in before the big itunesconnect UI change.  One of them was finally reviewed but rejected yesterday for crashing after startup.  Naturally, I can't reproduce the error but with the given crash logs I tweaked some code and resubmitted.

Then I went into iTunesConnect and could not find my binary.  Ah, wait - it's here under "Prereleases".  It says that it is "processing".  It's already been validated.  "Processing"?   What can it possibly be doing?  Theories abound:
  1. Each byte is meticulously separated and dropped into a "bit bucket" which then tips over and causes a matchbox car to go down a ramp, triggering a fan, which expands a small red balloon, thus delighting the children of Apple employees.
  2. The binary is transmitted to CIA for insertion of  top secret code which turns my flashcard app into a Morse code messenger for international spies.
  3. It's standing in the TSA line at Denver International Airport.
After some quick Goog research I find out that we have to wait for the "processing" step to finish before we can do anything else.

So, six hours later, the sprinkled unicorn dust finally settles on the build and it is done "processing".    Unfortunately, this occurs at 2AM, and like all good developers, I'm in bed, dreaming of a better world where these fancy, newfangled computer thingies are smart enough to sheperd a binary through the process all by itself.

In the "Prereleases" list, my newly blessed magical "processed" binary shows a little   !  in a box with no explanation.   Clicking/Hovering over it makes no difference.     Is it tired? Unhappy?

I add the binary to the app record, and go to subm -  ah, no.  Requires a "Save" first, then I can resubmit.   Because, you know, I might want to add a build and then get a sandwich before attempting that last, exhausting, tap-the-submit-button step.

I have three other apps "Waiting For Review" that are not even showing up in the app list as that status.   Another one that says it's "Waiting For Review" but it was actually approved before the UI change.    Yay! There are no rules!  Pick your own status!