tag:blogger.com,1999:blog-48372265654193366402024-03-04T21:12:05.505-08:00SaveMe.txtUnknownnoreply@blogger.comBlogger39125tag:blogger.com,1999:blog-4837226565419336640.post-15807049762594429102016-06-14T07:05:00.002-07:002016-06-14T07:05:19.687-07:00Setting up a Google Play Expansion File (.obb) for easy Android WebView reference<h4>
Scoping the problem</h4>
A PhoneGap app I'm working on has a lot of image and sound files. We have the iOS version working fine, but the Android version is roughly 200MB, a full 100MB over the maximum binary size allowed by Google Play.<br />
<br />
Google Play <a href="https://developer.android.com/google/play/expansion-files.html" target="_blank">tells us</a> that we can create a separate ".obb" (Opaque Binary Blob) expansion file where we can offload some of that content. The OBB is uploaded along with the APK,<br />
<br />
Ok, sounds great. But before creating the OBB, I know I'm going to have to access the files in the OBB from HTML passed to an Android WebView. Ideally, we want simple file references without writing a lot of code. For example:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;"><img src="file://SomePathToOBB/photos/photo1.png"></span><br />
<br />
I didn't want to have to unpack the OBB, so after doing some Stack Overflowing, <a href="http://stackoverflow.com/questions/10223674/loading-html-file-from-local-folder-into-webview" target="_blank">here</a> and <a href="http://stackoverflow.com/questions/11986335/using-images-from-apk-expansion-file-in-android-webview" target="_blank">here</a>, I decided to pursue this SO suggestion:<br />
<blockquote class="tr_bq">
<span style="font-size: small;"><i><span style="font-family: "arial" , "helvetica" , sans-serif;">It is a good alternative to use <a href="http://developer.android.com/tools/help/jobb.html" rel="nofollow">JOBB</a> tools to pack expansion file and use <a href="http://developer.android.com/reference/android/os/storage/StorageManager.html" rel="nofollow">StorageManager</a>
to mount it. After that the files inside expansion file should be
accessible as regular files in the file system. The unpacking of
expansion file is not needed in this case.</span></i></span></blockquote>
An OBB file can be of any type (.pdf, .zip, etc), but a file created with the JOBB utility can be mounted using StorageManager and requires no extra work at runtime (like extracting from a zip file).<br />
<br />
<h4>
Creating the .obb using JOBB & loading on a test device</h4>
To create the .obb, I moved directories of image files into a directory named <i>materials</i>, and then ran the following* from the command line on my Mac:<br />
<blockquote class="tr_bq">
<span style="font-family: "courier new" , "courier" , monospace;">/Users/mikem/Library/Android/sdk/tools/jobb -d materials -o <span style="font-family: "courier new" , "courier" , monospace;">my</span>.obb -pn com.gwhizmobile.example -pv 1</span> </blockquote>
<div style="text-align: justify;">
<i>* com.gwhizmobile.example represents the app package name </i></div>
<div style="text-align: justify;">
<br /></div>
Easy! The OBB was created as <span style="font-family: "courier new" , "courier" , monospace;">my</span><span style="font-family: "courier new" , "courier" , monospace;">.obb</span>. According to my reading of the docs, when I upload it to Google Play, it will automagically rename the file into something like <span style="font-family: "courier new" , "courier" , monospace;">main.1.com.gwhizmobile.example.obb</span>. However, I wanted to be able to test it on my Nexus 5 test device, so I renamed it into the proper format, and put it into a directory structure with the following path:<br />
<blockquote class="tr_bq">
<span style="font-family: "courier new" , "courier" , monospace;">./obb/com.gwhizmobile.example/main.1.com.gwhizmobile.example.obb</span></blockquote>
This structure allowed me to use the <a href="https://www.android.com/filetransfer/" target="_blank">Android File Transfer</a> utility for Mac to copy the <span style="font-family: "courier new" , "courier" , monospace;">obb</span> directory onto my device (in the Android folder)<br />
<br />
So at this stage, I have the .obb file built and residing on my Nexus 5 where it is supposed to be.<br />
<br />
<h4>
Using Storage Manager to mount the .obb and get contents path</h4>
Next, we need to add some code that allows us to mount the obb. Between <a href="http://stackoverflow.com/questions/14342068/how-to-mount-encrypted-apk-expansion-files" target="_blank">here</a> and <a href="http://www.codota.com/android/scenarios/51891809da0a211e8d11f672/android.os.storage.StorageManager?tag=out_2013_05_05_07_19_34" target="_blank">here</a>, I generated this little snippet of code**:<br />
<br />
<div style="background-color: white; color: black;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="font-size: small;"> <span style="color: navy; font-weight: bold;">String pathReference = null;</span></span></span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span style="font-size: small;"><span style="color: navy; font-weight: bold;"><br /></span></span></span>
<span style="font-family: "courier new" , "courier" , monospace;"><span style="font-size: small;"><span style="color: navy; font-weight: bold;"> final </span>StorageManager storageManager = (StorageManager) context.getSystemService(context.<span style="color: #660e7a; font-style: italic; font-weight: bold;">STORAGE_SERVICE</span>);</span></span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span style="font-size: small;"> <span style="color: navy; font-weight: bold;"> </span></span></span></div>
<div style="background-color: white; color: black;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="font-size: small;"><span style="color: navy; font-weight: bold;"> final </span>String packageName = <span style="color: green; font-weight: bold;">"com.gwhizmobile.example"</span>;<br /> <span style="color: navy; font-weight: bold;">final </span>String packageVersion = <span style="color: green; font-weight: bold;">"1"</span>;<br /><br /> <span style="color: navy; font-weight: bold;">final </span>File mainFile = <span style="color: navy; font-weight: bold;">new </span></span></span></div>
<div style="background-color: white; color: black;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="font-size: small;"><span style="color: navy; font-weight: bold;"> </span>File(Environment.<span style="font-style: italic;">getExternalStorageDirectory</span>() + <span style="color: green; font-weight: bold;">"/Android/obb/" </span>+ packageName + <span style="color: green; font-weight: bold;">"/"</span><span style="color: green; font-weight: bold;"></span>+ <span style="color: green; font-weight: bold;">"main." </span>+ packageVersion + <span style="color: green; font-weight: bold;">"." </span>+ packageName + <span style="color: green; font-weight: bold;">".obb"</span>);<br /><br /> storageManager.mountObb(mainFile.getAbsolutePath(), <span style="color: navy; font-weight: bold;">null</span>, <span style="color: navy; font-weight: bold;">new </span>OnObbStateChangeListener() {</span></span></div>
<div style="background-color: white; color: black;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="font-size: small;"><br /> <span style="color: olive;">@Override</span></span></span></div>
<div style="background-color: white; color: black;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="font-size: small;"><span style="color: olive;"><br /></span><span style="color: olive;"> </span><span style="color: navy; font-weight: bold;">public void </span>onObbStateChange(String path, <span style="color: navy; font-weight: bold;">int </span>state) {<br /> <span style="color: navy; font-weight: bold;">super</span>.onObbStateChange(path, state);<br /> <span style="color: navy; font-weight: bold;">if </span>(state == OnObbStateChangeListener.<span style="color: #660e7a; font-style: italic; font-weight: bold;">MOUNTED</span>) {<br /> <span style="color: #660e7a; font-weight: bold;">pathReference </span>= <span style="color: #660e7a;">storageManager</span>.getMountedObbPath(path);<br /> }<br /> }<br /> });</span></span></div>
<br />
<i>** Since this is a PhoneGap app, this code was encapsulated within a simple homegrown Cordova plugin.</i><br />
<h4>
Final integration steps</h4>
The generated <span style="font-family: "courier new" , "courier" , monospace;">pathReference</span> value looks something like <span style="font-family: "courier new" , "courier" , monospace;">file:///mnt/obb/123e187987192397831</span>. This is the prefix we can use in the HTML, as in:<br />
<blockquote class="tr_bq">
<span style="font-family: "courier new" , "courier" , monospace;"><img src="</span><span style="font-family: "courier new" , "courier" , monospace;"><span style="font-family: "courier new" , "courier" , monospace;">file:///mnt/obb/123e187987192397831</span>/photos/photo1.png"></span></blockquote>
Works. Like. A. Champ.<br />
<br />
<h4>
A few other notes</h4>
<ol>
<li>Although we created the .obb by pointing it at the <i>materials</i> directory, that directory name is not included in the filename path.</li>
<li>GooglePlay will not allow you to upload the OBB file with your first APK!? However, after doing another build process the OBB can be loaded.</li>
<li>Note that the packageVersion in the code above will need to change since GooglePlay will rename the OBB file to the version of the APK loaded at the same time as the OBB.</li>
</ol>
<br />
<br />
<br />Mikehttp://www.blogger.com/profile/10629095212090166059noreply@blogger.com3tag:blogger.com,1999:blog-4837226565419336640.post-71559473099425510912016-03-24T11:58:00.000-07:002016-05-02T07:01:37.103-07:00A Guide for Engaging in Political Discussions and Not Looking Like an IdiotDuring this 2016 political season, there's been a distressing outbreak of bad grammar, misspellings, mangled abbreviations, and poor reasoning on political websites and Twitter. Although the offenders are largely from Team Trump, there's a fair amount from other camps as well.<br />
With that in mind, I consider it my patriotic duty to improve the political
discourse in these United States, and I'll address that obligation in the form of this blog post. :) I welcome comments, suggestions, and corrections.<br />
<br />
Disclaimer: I don't pretend to be a grammarian, and some of this writing probably violates at least a few rules in <i>The Little Brown Handbook</i> - but somebody must take the first step - it might as well be me!<br />
<br />
Here goes, and may God bless America (and keep her literate).<br />
<br />
<b>GRAMMAR</b><br />
<br />
<b>You're</b> is the shortened form of <i>you are</i>, not <b>your</b>. <b>Your</b> is a possessive, as in: <i>Your lack of basic grammar skills makes it seem like you should be in summer school instead of commenting on presidential politics.</i><br />
<br />
<span class="_5mdd _1n4g"></span><b>They're</b> is the shortened form of <i>they are</i>. <b>Their</b> is a possessive<i> </i>- for example, <i>they are wasting <b>their</b> vote if they vote for your guy. </i>Additionally, <b>there</b> refers to a place, as in, <i><b>there</b> they go again</i>. <span class="_5mdd _1n4g"> </span><br />
<br />
<b>SPELLING</b><br />
<br />
States do not <b>succeed</b> from the union, they <b>secede</b>. Perhaps they successfully seceded?<br />
<br />
<b>Conceded</b> does not mean the same thing as <b>conceited</b>. All of us cool kids know this already.<br />
<br />
If you use <b>loosing</b> when you should have used <b>losing</b> then you've already lost.<br />
<br />
<b>Lieing</b> is not a word and I ain't <b>lying</b>. Same with <b>lier</b>.<br />
<br />
<span class="_5mdd _1n4g">The problem with illegal immigration is not <b>boarder</b> security, but <b>border</b> security. (Unless you're referring to a lack of rooms in your house).</span><br />
<br />
<span class="_5mdd _1n4g"><b>Traders</b> and <b>Traitors</b> are not the same. Traders trade; Traitors don't trait. </span><br />
<br />
<br />
<b>ABBREVIATED WORD MEANING</b><br />
<b> </b><span class="_5mdd _1n4g"> </span><br />
<span class="_5mdd _1n4g">There is no such thing as a <b>Super Pack</b> unless you're talking about <a href="http://store.steampowered.com/app/256320/" target="_blank">these</a>. You are probably referring to a <b>super PAC</b>, where PAC stands for Political Action Committee.</span><br />
<br />
A <b>Rhino</b> isn't the same as a <b>RINO</b> (Republican In Name Only). That thing on my face is a nose, not a horn. (although I can understand the confusion)<br />
<br />
<span class="_5mdd _1n4g"><b>i.e.</b> and <b>e.g.</b> are not the same. <b>i.e. </b>(</span><span class="_5mdd _1n4g"><i>id est)</i> translates to "that is", and <b>e.g.</b> (</span><span class="_5mdd _1n4g"><span class="st"><i>exempli gratia) </i></span>means "for example".</span><br />
<br />
<br />
<b>REASONING & ARGUMENTS</b><br />
<br />
Starting a sentence with <span class="_5mdd _1n4g"><i>The fact is...</i> implies that it will be followed by verifiable facts, not opinions. </span><br />
<br />
<span class="_5mdd _1n4g">Never use <b>never</b> and always avoid <b>always -</b> Using these adverbs weakens your argument since they are usually logically false. For example, saying something like "Conservatives never treat poor people fairly" or "Liberals always hate America" is demonstrably untrue.</span><br />
<span class="_5mdd _1n4g"><br /></span>
<span class="_5mdd _1n4g">Don't use strawman arguments (setting up a false proposition and then knocking it down). "Even though liberals want to take all of our guns, we need those weapons for self-defense!". "Even though conservatives want poor people to die early, we still need universal health care!"</span><br />
<br />
Calling someone a dumb-ass, liar, or other school yard taunt will not help your cause. It may make you feel better, and may even be true, but it will shut down any hope of educating your opponent to your point of view. What is the point of arguing if not to make your opponent see the light (or perhaps learn something)?<br />
<b><br /></b>
<b>NOT A WORD</b><br />
<br />
Hugely<br />
<br />
<br />
<br />Mikehttp://www.blogger.com/profile/10629095212090166059noreply@blogger.com2tag:blogger.com,1999:blog-4837226565419336640.post-88894701175093064512016-02-18T12:15:00.000-08:002016-03-28T05:37:14.512-07:00Facebook Audience Network Ad Approval(Feb/2016)<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
I reran the app - this time with it running on a local device connected to Xcode. On each refresh, it would write <span style="font-family: "courier new" , "courier" , monospace;">[FBAudienceNetworkLog/FBAdUtility:396] Impression is logged (displayed for test ads only</span>) to the console. <br />
<br />
I let it go for a few minutes until there were at least five of these impressions.<br />
<br />
Then, to my surprise, less than a minute or two later, I received a Facebook alert saying that the app had been approved.<br />
<br />
I had a second app that I repeated the process with. Again, after just a few minutes, the app was approved for ads. <br />
<br />
*** Update 3/28 ***<br />
<br />
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. Mikehttp://www.blogger.com/profile/10629095212090166059noreply@blogger.com2tag:blogger.com,1999:blog-4837226565419336640.post-74513494189803202572015-10-22T05:17:00.000-07:002015-10-23T05:22:12.739-07:00iOS 9 IssuesThis is just a list for keeping track of iOS 9 related issues I've run into.<br />
<br />
<br />
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)<br />
<br />
2) Weird issues with UIView hit test not propagating. Oddball things where it works everywhere but on an iPhone 6s. <strike>Generally, once recompiled, it works fine. </strike>Actually, I've found that recompiling or changing the hit test code does not work. In fact, any old style <span style="font-family: "Courier New",Courier,monospace;">touchesBegan</span> type tap handling does not work on the iPhone 6s.<br />
<br />
3) CSS Sibling selectors do not work in certain conditions, for example:<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">input:checked + label ~ div {<br /> z-index: 1;<br />}</span><br />
<br />
changing to:<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">input:checked + label + div {<br /> z-index: 1;<br />}</span><br />
<br />
does work, but doesn't work in iOS 8. Adding both rules seems to do the trick.<br />
<br />
4) Using <span style="font-family: "Courier New",Courier,monospace;">willRotateToInterfaceOrientation</span> to force a UISplitViewController to refresh the layout no longer works. Should use <span style="font-family: "Courier New",Courier,monospace;">setPreferredDisplayMode</span> instead, as in:<br />
<br />
<span style="font-size: xx-small;"><span style="font-family: "Courier New",Courier,monospace;"> <span style="font-size: x-small;">[sv setPreferredDisplayMode:(hideMaster)?UISplitViewControllerDisplayModePrimaryHidden : UISplitViewControllerDisplayModeAllVisible];</span></span></span><br />
<br />
for example.<br />
<br />
5) External http calls not working (after iOS 9 recompile). Need to add<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">NSAppTransportSecurity</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> NSAllowsArbitraryLoads : YES</span><br />
<br />
to info plist.<br />
<br />
<br />
<br />
<br />Mikehttp://www.blogger.com/profile/10629095212090166059noreply@blogger.com0tag:blogger.com,1999:blog-4837226565419336640.post-30836382774140100162015-10-06T09:39:00.004-07:002015-10-08T07:54:36.885-07:00How to use the UIFocusGuide for navigation<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;">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....</span><br />
<br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;">The focus guide, like the layout guide it extends, can be thought of as an invisible view - in this case, an invisible <i>focusable</i> view.</span><br />
<br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;">A UIFocusGuide functions as a bridge between different areas of the screen since focus navigation only works up/down or left/right.</span><br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;"><br /></span>
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;">Consider a screen that has two objects A and B. </span><br />
<br />
<span style="font-family: "Courier New", Courier, monospace;"> AAAA</span><br />
<br />
<span style="font-family: "Courier New", Courier, monospace;"> BBBB</span><br />
<br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;">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.</span><br />
<br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;">Thus, to make this work, you'd need a focus guide in between, like</span><br />
<br />
<br />
<span style="font-family: "Courier New", Courier, monospace;"> AAAA</span><br />
<br />
<span style="font-family: "Courier New", Courier, monospace;">FFFFFFFFFFFFFFFFFFFFF</span><br />
<br />
<span style="font-family: "Courier New", Courier, monospace;"> BBBB</span><br />
<span style="font-family: "Courier New", Courier, monospace;"> </span><br />
<br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;">and we would set the focus guide's preferredFocusedView = B to start. </span><br />
<br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;">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.</span><br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;"><br /></span>
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;">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.</span><br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;"><br /></span>
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;">Okay, now that you get the concept, what's the code look like? Something like this (Warning: I have not compiled/run this segment):</span><br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;"><br /></span>
<span style="font-family: "Courier New",Courier,monospace;"><br /></span>
<span style="font-family: "Courier New",Courier,monospace;">UIFocusGuide *F;</span><br />
<span style="font-family: "Courier New",Courier,monospace;"><br /></span>
<span style="font-family: "Courier New",Courier,monospace;">- (void)viewDidLoad</span><br />
<span style="font-family: "Courier New",Courier,monospace;">{</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> [super viewDidLoad];</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> </span><br />
<span style="font-family: "Courier New",Courier,monospace;"> F = [UIFocusGuide new];</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> [self.view addLayoutGuide:F];</span><br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"> [F.topAnchor constraintEqualToAnchor:A.bottomAnchor].active = YES;</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> [F.heightAnchor constraintEqualToConstant:1].active = YES;</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> [F.leftAnchor constraintEqualToAnchor:self.view.leftAnchor].active = YES;</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> [F.rightAnchor constraintEqualToAnchor:self.view.rightAnchor].active = YES;</span><br />
<span style="font-family: "Courier New",Courier,monospace;"><br /></span>
<span style="font-family: "Courier New",Courier,monospace;"> [F setPreferredFocusedView:B];</span><br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;"><span style="font-family: "Courier New",Courier,monospace;">}</span></span><br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;"><span style="font-family: "Courier New",Courier,monospace;">- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator</span></span><br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;"><span style="font-family: "Courier New",Courier,monospace;">{</span></span><br />
<br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;"><span style="font-family: "Courier New",Courier,monospace;"> if (context.nextFocusedView == A){</span></span><br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;"><span style="font-family: "Courier New",Courier,monospace;"> [F setPreferredFocusedView:B];</span></span><br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;"><span style="font-family: "Courier New",Courier,monospace;"> } else if (context.nextFocusedView == B){</span></span><br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;"><span style="font-family: "Courier New",Courier,monospace;"> [F setPreferredFocusedView:A];</span></span><br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;"><span style="font-family: "Courier New",Courier,monospace;"> }</span></span><br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;"><span style="font-family: "Courier New",Courier,monospace;">} </span></span><br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;"><br /></span>
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;"><br /></span>
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;">Apple
has a simple example in the UIKitCatalog demo that demonstrates the
concept in swift. The layout in that example case is more like:</span><br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">AAAAA BBBBB</span><br />
<br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;"><span style="font-family: "Courier New",Courier,monospace;">CCCCC FFFFF</span> </span><br />
<br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;">and it shows the navigation between B and C via F.</span><br />
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;"><br /></span>
Mikehttp://www.blogger.com/profile/10629095212090166059noreply@blogger.com0tag:blogger.com,1999:blog-4837226565419336640.post-45730003644238639722015-08-31T06:01:00.000-07:002015-08-31T06:01:22.276-07:00Upload a file to Google Drive via PHP using REST<i>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.</i> <br />
<br />
First off, Google does provide a <a href="https://developers.google.com/drive/web/quickstart/php" target="_blank">nice-looking PHP library</a> for working with Drive, but I was reluctant to introduce more libraries into our already complex and fragile server environment.<br />
<br />
Using REST, Google's <a href="https://developers.google.com/drive/web/manage-uploads" target="_blank">documentation</a> explains that there are three ways to upload files to Drive - using a <i>media</i> upload, a <i>multipart</i> upload, and a <i>resumable</i> upload. <br />
<br />
For our task, we were working with small spreadsheets so the <i>resumable</i> upload was overkill.<br />
<br />
We really wanted to use <i>multipart</i> but gave up after many hours of trying but getting only <span style="font-family: "Courier New",Courier,monospace;">400 - Bad Request<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;"></span></span> 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).<br />
<br />
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:<br />
<br />
<br />
<b>POSTing the file to upload</b>. (Assumes you have an <a href="https://developers.google.com/drive/web/about-auth" target="_blank">authorization token</a>). The result of the file_get_contents call will be a JSON structure that represents a <a href="https://developers.google.com/drive/v2/reference/files" target="_blank">file resource</a>. <br />
<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">$authstr = 'Bearer '.$token;</span><br />
<span style="font-family: "Courier New",Courier,monospace;"><br /></span>
<span style="font-family: "Courier New",Courier,monospace;">$fileToUpload = "datafiles/".$_FILES['uploadedFile']['name'].".csv";<br /></span><br />
<span style="font-family: "Courier New",Courier,monospace;">$url = 'https://www.googleapis.com/upload/drive/v2/files?uploadType=media&convert=true'; </span><br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">$options = array(<br /> 'http' => array(<br /> 'header' => "Content-type: application/vnd.ms-excel\r\nAuthorization: ".$authstr."\r\n",<br /> 'method' => 'POST',<br /> 'content' => file_get_contents($fileToUpload),<br /> ),<br /> );<br />$context = stream_context_create($options);<br />$result = file_get_contents($url, false, $context);</span><br />
<span style="font-family: "Courier New",Courier,monospace;">$json = json_decode($result);</span><br />
<br />
<br />
<b>PATCHing the file to change the title</b>. 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.<br />
<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">$url = 'https://www.googleapis.com/drive/v2/files/'.$json->id;<br /> </span><br />
<span style="font-family: "Courier New",Courier,monospace;">$options = array(<br /> 'http' => array(<br /> 'header' => "Content-type: application/json\r\nAuthorization: ".$authstr."\r\n",<br /> 'method' => 'PATCH',<br /> 'content' => '{"title" : "My New Filename"}',<br /> ),<br /> );<br /><br />$context = stream_context_create($options);</span><br />
<span style="font-family: "Courier New",Courier,monospace;">$result = file_get_contents($url, false, $context);<br />$json = json_decode($result);</span><br />
<br />
<br />
Have a better solution? I'd be interested to hear about it....<br />
<br />
<br />
<br />
<br />Mikehttp://www.blogger.com/profile/10629095212090166059noreply@blogger.com0tag:blogger.com,1999:blog-4837226565419336640.post-43485473894431620752015-05-28T06:44:00.000-07:002015-05-28T06:44:41.215-07:00Backing up Cordova databases, local storage, and CDVLocalStorage on iOSOn 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.<br />
<br />
Another note is that if you're building with Xcode, the BackupWebStorage flag is in the config.xml in the Staging area.<br />
<br />
Anyways, there are three available values, none, local, and cloud. <br />
<br />
When set to <b>none</b>, 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. <br />
<br />
<b>local</b> has the most complex activity. When set to <b>local</b>, 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.<br />
<br />
When BackupWebStorage is set to <b>cloud</b>, the following is set:<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"WebKitStoreWebDataForBackup"];</span><br />
<br />
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.<br />
<br />
<br />
CDVLocalStorage also creates various Cloud/NoCloud directories but it appears that none of these are ever used.<br />
<br />Mikehttp://www.blogger.com/profile/10629095212090166059noreply@blogger.com0tag:blogger.com,1999:blog-4837226565419336640.post-50708233811866119772014-12-04T13:35:00.000-08:002014-12-04T13:35:56.158-08:00A few more autolayout tips and tricks<br />
<br />
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. <br />
<br />
2. I have <strike>wasted many hours of my life that I'll never get back</strike> 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). <br />
<br />
Several StackOverflow posts recommend checking off the "<i>Remove at build time</i>" flag for constraints that should be set or adjusted programmatically. That will work, but it means I'll <b>always</b> 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)<br />
<br />
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.<br />
<br />
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 <strike>@#!$&$!</strike> default IB constraints. See #2.<br />
<br />
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<br />
<br />
5. And so I don't forget, here's how to position two views at 25% and 75% of parent width with programmatic constraints:<br />
<br />
<span style="font-size: small;"><span style="font-family: "Courier New",Courier,monospace;"> [buttonPanel addConstraint:[NSLayoutConstraint constraintWithItem:button1 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:buttonPanel attribute:<b>NSLayoutAttributeCenterX</b> <b>multiplier:.5</b> constant:0]];<br /><br /> [buttonPanel addConstraint:[NSLayoutConstraint constraintWithItem:button2 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:buttonPanel attribute:<b>NSLayoutAttributeCenterX</b> <b>multiplier:1.5</b> constant:0]];</span></span><br />
Mikehttp://www.blogger.com/profile/10629095212090166059noreply@blogger.com0tag:blogger.com,1999:blog-4837226565419336640.post-87328979771999647332014-09-09T05:27:00.001-07:002014-09-09T05:27:09.272-07:00Venting on the iOS8 iTunesConnect<br />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.<br />
<br />
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:<br />
<ol>
<li>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. </li>
<li>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.</li>
<li>It's standing in the TSA line at Denver International Airport.</li>
</ol>
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.<br />
<br />
So, <b>six hours later</b>, 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.<br />
<br />
In the "Prereleases" list, my newly blessed magical "processed" binary shows a little <span style="font-size: large;"><span style="background-color: yellow;"> <span style="background-color: yellow;"><b>!</b></span> </span></span> in a box with no explanation. Clicking/Hovering over it makes no difference. Is it tired? Unhappy?<br />
<br />
I add the binary to the app record, and go to subm - ah, no. Requires a "Save" first, <i>then</i> 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.<br />
<br />
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!<br />
<br />
Mikehttp://www.blogger.com/profile/10629095212090166059noreply@blogger.com0tag:blogger.com,1999:blog-4837226565419336640.post-45233894090490723052014-07-07T11:58:00.001-07:002014-07-07T11:58:48.436-07:00UIWebView issue with links only being called once.<br />
<br />
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. <br />
<br />
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.<br />
<br />
With some investigation, it seems that the delegate method <span style="font-family: "Courier New",Courier,monospace;">shouldStartLoadWithRequest</span> 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.<br />
<br />
It seems the issue is that, even if you return <span style="font-family: "Courier New",Courier,monospace;">NO</span> from <span style="font-family: "Courier New",Courier,monospace;">shouldStartLoadWithRequest</span>, 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.<br />
<br />
To fix this, I found the following <a href="http://stackoverflow.com/questions/1840355/uiwebview-shouldstartloadwithrequest-only-called-once">StackOverflow link</a> - 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.<br />
<br />
<code>
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{ </code><br />
<br />
<br />
<code> NSString *surl = [[request URL] absoluteString]; </code><br />
<br />
<code> NSRange r = [surl rangeOfString:@"#MYACTIONLINK"]; </code><br />
<code> if (r.location != NSNotFound){ </code><br />
<br />
<code> // do my custom action </code><br />
<code> [[NSNotificationCenter defaultCenter] postNotificationName:NotifyHTMLViewerSelectedLink object:nil userInfo:nil]; </code><br />
<br />
<code> [webView stringByEvaluatingJavaScriptFromString:@"window.location='#__DUMMY_ANCHOR'"]; </code><br />
<code> </code><br />
<code> return NO; </code><br />
<br />
<code> } </code><br />
<br />
<code> if ([surl hasSuffix:@"_DUMMY_ANCHOR"]) </code><br />
<code> return NO; </code><br />
<br />
<code>....
</code>Mikehttp://www.blogger.com/profile/10629095212090166059noreply@blogger.com0tag:blogger.com,1999:blog-4837226565419336640.post-11913223340799223932014-03-13T08:06:00.000-07:002014-03-13T08:06:06.932-07:00Percentage based layouts using (mostly) Xcode IB and AutolayoutThe 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.<br />
<br />
---------------------------------------------------<br />
| | |<br />
| | |<br />
| | |<br />
| A | B |<br />
| | |<br />
| | |<br />
| | |<br />
--------------------------------------------------- <br />
<br />
Here's one way to use IB for all but a few lines of code. (based on ideas from <a href="http://stackoverflow.com/questions/18174980/creating-a-percentage-based-ios-layout">this stackoverflow post.</a>)<br />
<br />
For "view B", using Xcode IB, I put constraints on the top, right, bottom, and left<br />
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.<br />
<br />
then, in the view controller's viewDidLoad, I added these two lines:<br />
<br />
<br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> NSLayoutConstraint *c = [NSLayoutConstraint constraintWithItem:viewA<br /> attribute:NSLayoutAttributeWidth<br /> relatedBy:NSLayoutRelationEqual<br /> toItem:viewA.superview<br /> attribute:NSLayoutAttributeWidth<br /> multiplier:.25<br /> constant:(CGFloat)0];<br /> <br /><br /> [viewA.superview addConstraint:c];</span></span><br />
<br />
<br />
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.Mikehttp://www.blogger.com/profile/10629095212090166059noreply@blogger.com1tag:blogger.com,1999:blog-4837226565419336640.post-57710659502982027322014-03-11T09:14:00.002-07:002014-03-27T12:52:38.952-07:00iOS 7.1 issues<br />
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. <br />
<br />
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;<br />
<br />
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.<br />
<br />
<br />
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:<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">Cannot find an outgoing row head for incoming head UILabel:0x1462c580.Width{id: 414}, which should never happen.' </span><br />
<br />
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).<br />
<br />
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. <br />
<br />
<br />Mikehttp://www.blogger.com/profile/10629095212090166059noreply@blogger.com0tag:blogger.com,1999:blog-4837226565419336640.post-86679773279577803142014-02-16T10:45:00.001-08:002014-02-16T11:15:06.480-08:00PERL Matching non-ASCII characters in a converted RTFI 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.<br />
<br />
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".<br />
<br />
To try and figure out what the bytes/characters were, I created a little PERL code segment that looked like:<br />
<span style="font-family: "Courier New",Courier,monospace;"><br /></span>
<span style="font-family: "Courier New",Courier,monospace;">while ($filecontents =~ /([^\d\w\s\t\.:;&\,\-\(\)]+)/){<br /> $f = $1;<br /> $d = $1;<br /> <br /> $f =~ s/(.)/sprintf("%x ",ord($1))/eg;<br /> print "f is $f\n";<br /> $filecontents =~ s/$d/zzz/g;<br /><br />}</span><br />
<br />
Basically, the code goes thru the file, finds oddball characters and prints them out. When I ran it, it produced the following:<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"> f is e2 80 83 <br /> f is e2 80 a8 <br /> f is e2 81 84 <br /> f is c2 b0 </span><br />
<br />
Note that each of those looks like a multi-byte character, but what are they?<br />
<br />
Well, I do love the internet. I cut and pasted <span style="font-family: "Courier New",Courier,monospace;">e2 80 a8</span> into Google and found that it was an "em space", aka Unicode character<span style="font-family: "Courier New",Courier,monospace;"> \u2003</span>. <br />
<br />
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).<br />
<br />
Note that, at least in my case, I had to match using the hex versus the unicode character. In other words<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"> $filecontents =~ s/\xe2\x80\xa8/ /g;</span><br /><br />
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 :)Mikehttp://www.blogger.com/profile/10629095212090166059noreply@blogger.com0tag:blogger.com,1999:blog-4837226565419336640.post-14336485513930585922014-02-10T07:10:00.001-08:002014-02-10T07:10:38.168-08:00UIWebView, URL history, and redirectsSometimes it seems the simplest things turn out to be much more complicated than they should be.<br />
<br />
In several of our apps, we include help files written in HTML which are loaded locally from the bundle into a UIWebView. Sometimes those help files contain links to web pages.<br />
<br />
The problem is that UIWebView doesn't treat locally loaded webpages as part of the history stack. Thus if the user clicks a link and visits a web page, there is no simple way for the app to return to the original locally loaded HTML file because <span style="font-family: "Courier New",Courier,monospace;">[webview canGoBack]</span> returns <span style="font-family: "Courier New",Courier,monospace;">NO</span>. Grr.<br />
<br />
My first attempt to deal with the issue was to just reload the local file if <span style="font-family: "Courier New",Courier,monospace;">[webview canGoBack] </span>was <span style="font-family: "Courier New",Courier,monospace;">NO</span>. However, if the same locally loaded HTML file contains two web pages, and the user visits each, after the attempt to return from the 2nd webpage, <span style="font-family: "Courier New",Courier,monospace;"> [webview canGoBack]</span> will return <span style="font-family: "Courier New",Courier,monospace;">YES</span> and <span style="font-family: "Courier New",Courier,monospace;">[webview goBack]</span> will display the first webpage, because the first webpage was never removed from the UIWebView's history, and there's apparently no way (that I was able to find) to get rid of it. Grrr.<br />
<br />
The next step was to try and maintain my own count of visited URL's and back out of them as the user clicked the back button. I implemented this by adding to the count in the UIWebView delegate method <span style="font-family: "Courier New",Courier,monospace;">shouldStartWithRequest</span> when the <span style="font-family: "Courier New",Courier,monospace;">navigationType</span> was <span style="font-family: "Courier New",Courier,monospace;">UIWebViewNavigationTypeLinkClicked</span> and removed them with each click of the back button. Great! Except that redirects also load with a <span style="font-family: "Courier New",Courier,monospace;">navigationType</span> of <span style="font-family: "Courier New",Courier,monospace;">UIWebViewNavigationTypeLinkClicked</span> so the user would have to click "back" several times for no clear reason because the link count was incremented for each redirect. Grrrr.<br />
<br />
I was able to finally make it work with some insight from <a href="http://iosdeveloperzone.com/2011/05/22/tutorial-building-a-web-browser-with-uiwebview-part-3/">this tutorial</a>. The key I learned was that the <span style="font-family: "Courier New",Courier,monospace;">webViewDidFinishLoad</span> delegate method is not called until the redirects have been resolved. (But it may call the method several times as it loads the contents of the page).<br />
<br />
Below are the key elements of the code:<br />
<br /><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType<br />{<br /> <br /> // we use the linkClicked boolean because with redirects, this method is still called multiple times and we only want <br /> // to increment once.<br /> if (linkClicked == NO && navigationType == UIWebViewNavigationTypeLinkClicked){<br /> linkClicked = YES;<br /><br /> if (linkStack == 0){<br /> scrollOffset = webView.scrollView.contentOffset;<br /> }<br /> linkStack++;<br /> }<br /> <br /> return YES;<br />}<br />- (void)webViewDidFinishLoad:(UIWebView *)webView<br />{<br /> if (linkStack == 0 && scrollOffset.y > 0){<br /> // reset the scroll if we are coming back to a page after a clicked link.<br /> [wv.scrollView setContentOffset:scrollOffset];<br /> scrollOffset = CGPointMake(0, 0);<br /> }<br /> <br /> NSURLRequest* request = [webView request];<br /> if ([[request mainDocumentURL] isEqual:lastMainDoc])<br /> return;<br /> <br /> linkClicked = NO;<br /> [self setLastMainDoc:[request mainDocumentURL]];<br /> <br /> NSLog(@"finished loading %@", [[request mainDocumentURL] absoluteString]);<br /> <br />}<br /><br />-(void)doBack:(id)sender<br />{<br /> <br /> if (linkStack > 0){<br /> if (linkStack == 1 || [wv canGoBack] == NO){<br /> [self loadFile];<br /><br /> } else {<br /> linkStack--;<br /> [wv goBack];<br /> }<br /> return;<br /> <br /> }<br />}<br /><br /><br />- (void)loadFile<br />{<br /> // load local HTML file<br /> //....<br />}</span></span><br />
<br />
<br />
I'm sure there are a few other ways to address this problem, but this is what worked for me....Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4837226565419336640.post-13989562219013171402013-02-08T12:26:00.003-08:002013-02-08T12:34:57.232-08:00Using -webkit-tap-highlight-colorIn earlier versions of mobile safari if you used <span style="font-family: "Courier New",Courier,monospace; font-size: small;">-webkit-tap-highlight-color</span> on a link, you'd also have to specify the active color. That would give you a nice contrast. However, since about iOS 5 that no longer works - both the foreground and background are set to the highlight color.<br />
<br />
To be able to see the text now, it seems that you need a transparent color. This works:<br />
<span style="font-size: small;"><br /></span>
<span style="font-family: "Courier New",Courier,monospace; font-size: small;">-webkit-tap-highlight-color:rgba(26,26,26,.5);</span><br />
<br />
<br />Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4837226565419336640.post-4247612924730878562013-02-06T16:22:00.000-08:002013-02-06T16:22:29.641-08:00Wrapping text around an image AND stopping the wrap.<br />
I like being able to use HTML to wrap text around images. It looks professional and it's a simple way to inject some cool in an HTML page.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6rnAZXtu4Iy3x7S6w3VB1PxRklguAbLUHIHubZaXwGJue0ZJtoc03OC7SzcabaakrwrffKODAuU_DCsJB9HaJPagCi6weV9uz8e5K56oDeBa9RFw1Vi-WZuchIwguII4bCoIXt8D9cnbL/s1600/xmark.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6rnAZXtu4Iy3x7S6w3VB1PxRklguAbLUHIHubZaXwGJue0ZJtoc03OC7SzcabaakrwrffKODAuU_DCsJB9HaJPagCi6weV9uz8e5K56oDeBa9RFw1Vi-WZuchIwguII4bCoIXt8D9cnbL/s320/xmark.png" width="320" /></a></div>
However, sometimes the image is bigger than the text, and at that point you may want to start a new paragraph that is beyond the image. I was never able to find out how until today: Just needs<br />
<br />
<code>
<BR CLEAR="left">
</code><br />
<br />
<br clear="left">
See? Down here now! Note that "left" can be other values (e.g., "right","all") to configure appropriately.
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4837226565419336640.post-42545564061986543762013-02-01T06:17:00.002-08:002013-02-01T06:17:58.508-08:00Using Google TTS (text to speech)I was doing some investigation on Text-To-Speech (TTS) for iOS and found a little snippet of code that could be added to any app for some quick TTS.<br />
<br />
The catch is that it's limited to just 100 characters and you never know when Google might pull the plug on it, but still, it's pretty cool.<br />
<br />
<br />
<code>
#import <AVFoundation/AVFoundation.h>
<br /><br />
...
<br /><br />
NSString *linkTTS = [NSString stringWithFormat:@"http://translate.google.com/translate_tts?tl=en&q=%@",@"this+is+really+quite+cool"];<br /> <br /> NSData *dataTTS = [NSData dataWithContentsOfURL:[NSURL URLWithString:linkTTS]];<br /> <br /> AVAudioPlayer *_googlePlayer = [[AVAudioPlayer alloc] initWithData:dataTTS error:nil];<br /> [_googlePlayer play];
</code>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4837226565419336640.post-81545418755983671962012-06-15T08:54:00.000-07:002012-06-15T08:54:22.978-07:00Add Localized Help Files Maintaining Subfolder Hierarchy in XCode 4One of my projects has localized help files for the app. When the help was originally designed, it was put into subfolders for major help topics. That didn't really cause any problems until I migrated it into XCode 4. There it took a little bit of experimentation before I figured it out.<br />
<br />
Basically I had to<br />
<ol>
<li>Click on the target</li>
<li>Select File->Add Files to...</li>
<li>Add the en.lproj/help directory and then make sure to <i>Create folder references for any added folders</i></li>
<li>Then, select the newly created blue <i>help </i>item in the project navigator<i> </i>and then click on the File->Add Files to.. for the other languages (e.g., es.lproj/help) etc. If you try to do this by clicking on the target first (instead of the <i>help </i>item in the navigator) it'll cause a compiler error since it (apparently) tries to overwrite the subdirectories</li>
</ol>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4837226565419336640.post-39498973483214663472012-04-25T17:10:00.002-07:002012-04-25T17:10:49.852-07:00WWDC 2012 - A Guide For Those That Can't GoFor those of us that didn't get WWDC tickets, I've decided to write up some tips to make us feel like we're there (based on my experience the last two years). So let's keep our chins up, eh? Here goes:
<ol>
<p><b>NOW</b>
<li> Send me $1600.00. Part of WWDC's mystique is writing a large check for others to tell you what to do. This is a necessary step. Make sure you do it.
<p><B>THE DAY BEFORE</B>
<li> Register. Have your S.O. misspell your name on a small piece of paper and attach it with a shoestring. Write WWDC 2012 on a piece of clothing (jacket, t-shirt) and wear it EVERYWHERE for the next five days.
<p><B>THE FIRST DAY</B>
<li> Form a single file line outside of your house (you may need to draw on kids, pets, etc). This will simulate the early gathering of dedicated engineers outside of Moscone before the keynote speech. While Tim Cook might not be as big of a draw as Steve was (RIP Steve), you may still want to get in line no later than 11PM the night before; you'll want to be able to sit up front.
<p>If you can get a neighbor to walk down your "line" and offer you magazines no one reads, that would be ideal.
<li> Once inside Moscone (your house) walk into a small closet filled with clothes, lukewarm coffee, a few bagel fragments and a banana. This represents the "pre-keynote" activity where everyone is standing in the grand hallways. It does get a little tight. Stand there until you get tired, and then sit down and pull out your laptop. Complain about the Wifi.
<li> After 50 minutes or so, get up and walk out of the closet and go upstairs for the keynote. Hurry and find a seat. Stragglers will need to stay downstairs in "the overflow room" since the ballroom will be filled.
<li> When the keynote starts, you may find yourself clapping for no particular reason. Write something clever on your iPad and hold it over your head. Smile a lot. You're at WWDC!!!
<p><B>POST-KEYNOTE LUNCH</B>
<li> Go downstairs. Form a line in front of a table with 5000 boxes of food. After 15 minutes, grab a box of food and attempt to find a seat. Sit on the floor if there are no tables. You can get a drink but, for maximum realism, you should only stock Diet Sprite.
<p><B>ATTEND A SESSION</B>
<li> Pick a session and form a line outside your den.
<p>Uh oh. You're turned away at the door; this session is full. Stand in line outside your bedroom instead. Listen to a session talking about hardware optimization on the Newton. It's okay to leave early.
<p><B>OTHER ACTIVITIES</B>
<li> Set up a pseudo Apple knick-knacks and branded clothing store. Be sure to overcharge yourself.
<p> <B>ATTEND THE THURSDAY NIGHT EVENT</B>
<li> Hang out with 5,000 of your closest friends and listen to a band only five of you have ever heard of. Smile a lot. You're at WWDC!!!
</ol>
<i>Seriously though, I enjoyed (and learned a lot at) WWDC the last couple of years. Maybe I'll go again next year :)</i>Unknownnoreply@blogger.com1tag:blogger.com,1999:blog-4837226565419336640.post-50764729761940830972012-04-07T14:00:00.003-07:002012-04-07T14:02:37.360-07:00presentModalViewController prevents release of other view controllersNot totally sure what's going on, but I have a split view with a control panel on the left and a detail panel on the right. I have a situation where I want to clear the detail panel and then popup a modal view controller. However, it looks like the modal view controller is somehow causing another retain to be added to the view controller in the detail panel, and then it never releases it. <br /><br />If I clear the detail panel AFTER calling presentModalViewController, then the detail panel view controller is cleared normally.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4837226565419336640.post-58023989361345022972012-03-06T14:06:00.002-08:002012-03-06T14:12:30.323-08:00Sooner or later I'll learn. Force a memory error when testingBecause in the real world, when a memory warning causes the parent windows to give up their resources, it causes viewDidLoad to be run again when the parent view appears. This can cause oddball side effects so this should be tested with every app before release.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4837226565419336640.post-57813471098059788622012-02-26T14:13:00.003-08:002012-02-26T14:19:00.904-08:00More text encoding issuesIs it me or is TextEdit for the mac just not very good at figuring out text encodings?<br /><br />I'm trying to create a new HTML file from a word doc that has spanish characters in it. If I cut and paste into TextEdit, that works/looks fine, but if I try to display in Safari the character mapping is off. However if I then save the TextEdit file as UTF-8 and then use iconv to convert to ISO-8859-1, then all is fine. Trying to save the TextEdit file as Western OS doesn't do the trick.<br /><br />I did try to add a meta charset tag to the HTML, but either I did it wrong or it was ignored.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4837226565419336640.post-88901997848992114682012-01-11T07:41:00.000-08:002012-01-11T08:07:51.609-08:00Perl, UTF-8, and TextEdit character encoding hellI'm working on a project where I need to dynamically read in content from a flat file. The flat file contains some template information and then I'm also pasting in the contents (using TextEdit) of a file that was generated using Perl database access. <br /><br />The problem was that when I read it into a string using stringWithContentsOfFile and NSUTF8StringEncoding it would blow up. I could use NSASCIIStringEncoding, but then some of the characters (e.g em dash, single double quote) were translated incorrectly. If I brought this file up in TextEdit or Dashcode, everything looked great. Displaying the file in vi or the command line did not. <br /><br />When I did a <code>file -I <i>foo.txt</i></code> it reported the file type was "unknown" although the Perl generated file was utf-8.<br /><br />I traced the problem down to the TextEdit "Plain Text File Encoding" preferences. Both "Opening Files" and "Saving Files" were set to UTF-8. This was helpful to read the data, but somehow when saving the pasted-in content, it caused the file type to get hosed such that certain tools (e.g. my command window which is set to UTF-8) could no longer properly read the characters.<br /><br />Once I set the preferences back to automatic, the saved file is now utf-8, but the special characters don't display correctly in TextEdit. The file also loads with UTF8 encoding into an NSString.<br /><br />Weird. <br /><br />That's three hours of my life I'll never get back.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4837226565419336640.post-21224840878410691402011-11-10T11:57:00.000-08:002011-11-10T12:04:55.675-08:00Dealing with expired developer and distribution certificatesSo, round this time every year for the last few, I go into a small panic as I try to remember the steps to recreate my development and distribution certificates.<br /><br />Fortunately, after cruising various blogs and the Developer Forums I saw a reference to this document:<br /><br /><a href="http://developer.apple.com/library/ios/#technotes/tn2250/_index.html%23//apple_ref/doc/uid/DTS40009933">http://developer.apple.com/library/ios/#technotes/tn2250/_index.html%23//apple_ref/doc/uid/DTS40009933</a><br /><br /><br />I worked thru the section on <i>Deleting/Revoking Your Certificates and Starting Fresh</i> and was done in about 10 minutes.<br /><br />Waaayyy easier than anything else I've seen out there...Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4837226565419336640.post-59568391452039419702011-10-26T05:41:00.001-07:002011-10-26T05:42:51.808-07:00password protect a zip file on the macFrom the command line: zip -re foo.zip file1.txt file2.txt<br /><br />you'll be prompted for a passwordUnknownnoreply@blogger.com0