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!

Monday, July 7, 2014

UIWebView issue with links only being called once.



We use UIWebView's pretty heavily in our apps.  Sometimes we use links in a page to invoke other behaviors, like pushing another UIViewController on to the navigation stack.

We noticed recently that, on an iPad, clicking such a link, popping the navigation stack, then trying to click the link again did not work.

With some investigation, it seems that the delegate method shouldStartLoadWithRequest was only being called the first time.  If another link on that same page was clicked, it would work fine; the problem only occurred when the same link was attempted two or more times consecutively.

It seems the issue is that, even if you return NO from shouldStartLoadWithRequest, UIWebView believes that the requested link is the currently viewed page - thus, clicking the link again takes no action since UIWebView thinks that it's already there.

To fix this, I found the following StackOverflow link - it describes a similar problem.  The workaround to fix the issue is to fool UIWebView into believing that it is somewhere else by using javascript to change its location.

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { 


    NSString *surl = [[request URL] absoluteString]; 

    NSRange r = [surl rangeOfString:@"#MYACTIONLINK"]; 
    if (r.location != NSNotFound){ 

    // do my custom action
        [[NSNotificationCenter defaultCenter] postNotificationName:NotifyHTMLViewerSelectedLink object:nil userInfo:nil]; 

        [webView stringByEvaluatingJavaScriptFromString:@"window.location='#__DUMMY_ANCHOR'"]; 
    
        return NO; 

    } 

    if ([surl hasSuffix:@"_DUMMY_ANCHOR"]) 
        return NO; 

....

Thursday, March 13, 2014

Percentage based layouts using (mostly) Xcode IB and Autolayout

The Xcode lnterface Builder (IB) doesn't provide a clear way to handle a percentage based layout - for example, a screen where one view takes up 25% of the width of the screen, and the other view gets the rest.

---------------------------------------------------
|                    |                                              |
|                    |                                              |
|                    |                                              |
|        A         |                       B                     |
|                    |                                              |
|                    |                                              |
|                    |                                              |
---------------------------------------------------

Here's one way to use IB for all but a few lines of code.  (based on ideas from this stackoverflow post.)

For "view B", using Xcode IB, I put constraints on the top, right, bottom, and left
For "view A", using Xcode IB, I put constraints on the top, left, bottom, and then set the width to a fixed size.  I edited the width constraint of "view A", and checked the "placeholder remove at build time" checkbox.

then, in the view controller's viewDidLoad, I added these two lines:


    NSLayoutConstraint *c = [NSLayoutConstraint constraintWithItem:viewA
                           attribute:NSLayoutAttributeWidth
                           relatedBy:NSLayoutRelationEqual
                            toItem:viewA.superview
                            attribute:NSLayoutAttributeWidth
                            multiplier:.25
                            constant:(CGFloat)0];
   

    [viewA.superview addConstraint:c];



This just makes viewA 25% of the size of it's superview.  Since viewB is tied to viewA's width, it also adjusts properly without having to specify extra constraints for it.

Tuesday, March 11, 2014

iOS 7.1 issues


x. Noticed that the tab bars don't work quite the same way.  I have an .xib that I use for both iPad and iPhone layouts.  With 7.1, the tab bar now appears to be larger on the iPad (56 pixels) but the .xib doesn't auto adjust the 'y' for the former size (which was 49 pixels).  What this does is make the tab bar looked clipped at the bottom on the iPad. 

The fix requires using autolayout to set up the tab bar.  Then it works for 7.1 and prior versions.   An alternative fix is checking the tab bar's y value in the view controller's viewWillLayoutSubviews method to make sure it is equal to viewController.view.frame.size.height - tabBar.frame.size.height;

x. In previous versions, setting the corner radius and border on a layer would implicitly mask that layer to the corner arcs.  This is no longer the case in 7.1.  Now, if you set the background color of a UIView, and then set the corner radius and a border, the border will show the corner arcs, but the background color will extend to the square corners.


x. Not sure if this is a 7.1 only, issue but noted that an application that used to work okay is now getting a weird autolayout error on the iPad when dismissing a modal view.  There's tons of unhelpful error diagnostics and it ends with:
 
Cannot find an outgoing row head for incoming head UILabel:0x1462c580.Width{id: 414}, which should never happen.'

I saw a few mentions of this error on the web but no real help.  I was able to avoid the error by changing the constraints on one of the ViewControllers' UILabels.  Originally the UILabel was constrained to top right left with a fixed height.  It works if set to top width height centered x.     I doubt that this is the "real" solution, but I wasn't able to find anything else that seemed to be the issue.   (Ugh. There's three hours of my life I'll never get back).

Update: Ran into this error again and burned another six or so hours.  It seems related to percentage based views in autolayouts but I haven't been able to narrow it down much further than that.


Sunday, February 16, 2014

PERL Matching non-ASCII characters in a converted RTF

I have a data file that was converted from an RTF to a TXT.    When I started trying to parse it using PERL, my regular expressions weren't able to split up lines that looked like they had whitespace delimiters - It would just ignore the whitespace.

After my initial confusion, I figured that the whitespace must be something other than an ASCII space character, tab, etc.   By some experimentation, I noticed that there were several bytes being represented in that "whitespace".

To try and figure out what the bytes/characters were, I created a little PERL code segment that looked like:

while ($filecontents =~ /([^\d\w\s\t\.:;&\,\-\(\)]+)/){
    $f = $1;
    $d = $1;
   
    $f  =~ s/(.)/sprintf("%x ",ord($1))/eg;
    print "f is $f\n";
    $filecontents =~ s/$d/zzz/g;

}


Basically, the code goes thru the file, finds oddball characters and prints them out.  When I ran it, it produced the following:

   f is e2 80 83
   f is e2 80 a8
   f is e2 81 84
   f is c2 b0 

 
Note that each of those looks like a multi-byte character, but what are they?

Well, I do love the internet.  I cut and pasted e2 80 a8  into Google and found that it was an "em space", aka Unicode character \u2003.

Once I was able to get the Unicode character, I could just replace all of the em spaces with a regular space, and the rest of my program worked as designed.  Same idea with the other special characters.  Two of those characters were not whitespace, but were non-ASCII characters as well (fraction slash and degree symbol).

Note that, at least in my case, I had to match using the hex versus the unicode character. In other words

    $filecontents =~ s/\xe2\x80\xa8/ /g;

I'm assuming this is because the Unicode would be a UTF-16 character but I'm dealing with a UTF-8 encoding?   For next time, I should see if I can export the RTF to a UTF-16 text file.  Maybe it would be easier :)