Alexander Sergeev's blog

Talerka iOS 7.0 look&feel support

I did not only enjoy vacation during winter holidays. I worked hard on Talerka iOS application and we did release a new version which supports iOS 7-look and also features side menu.

The good things about side menu that it allows you to use much more space for content:

iOS Simulator Screen shot 18.01.2014, 16.42.16

more space on recipe screen – easier to read

iOS Simulator Screen shot 18.01.2014, 16.42.35

More space on search screen gave as a possibility to increase the heigh of table’s rows; thus, it became easier to tap on them

 

The bad thing is that I’ve broken (unintentionally) a iOS5 support. The update is on the way – after it becomes available the application will support all iOS vesrions from 5.0 to 7.0.

Share

Dealing with AdressBook on iOS 6.0: ABAddressBookRequestAccessWithCompletion

As you know, we have to use ABAddressBookRequestAccessWithCompletion call in order to get access to an Address Book in iOS 6.0 and higher:

void ABAddressBookRequestAccessWithCompletion (
ABAddressBookRef addressBook,
ABAddressBookRequestAccessCompletionHandler completion
);

(from iOS developer documentation)

It is not oblivious that the completion handler will be called on the arbitrary queue:

Discussion
Use this function to request access to address book data. This call will not block while the user is being asked for access, allowing your app to continue running. Until access has been granted, any address book references your app has will not contain any data, and any attempt to modify data will fail with an error type of kABOperationNotPermittedByUserError. The user is only asked for permission the first time you request access. Later calls use the permission granted by the user.

The completion handler is called on an arbitrary queue. If your app uses an address book throughout the app, you are responsible for ensuring that all usage of that address book is dispatched to a single queue to ensure correct thread-safe operation.

so you have to synchronize the execution somehow.

The following approach, introduced by Josephus here does not work for me: it locks the application completely if called on a main thread – it looks like the blocking of a main thread interferes with a system Address Book alert. Thus, we have decided to use the following code to solve the problem:


if (isIOS6orHigher) {
// System alert for iOS >= 6.0
ABAddressBookRequestAccessWithCompletion(addressBook_, ^(bool granted, CFErrorRef error) {
// We are on arbitrary queue
dispatch_async(dispatch_get_main_queue(), ^{
// We are on main queue
[self processAccessResponce:granted];
});
});
} else {
// Custom alert view for iOS < 6.0
...
}

 

Share

Valgrind and iOS

I’m working in Mail.ru now. My primary task is to fix memory management and concurrency problems in a complicated project written by a team of six people.

I had a nice experience using Valgrind tool on Linux system, so I started to investigate to possibility of using Valgrind with iOS.

The results are:

  • Valgrind works only on Mac OS X 10.7 Lion (Mac OS X 10.8 Mountain Lion is not supported yet).
  • Valgrind works only in simulator. There is no way to run it on a iPhone/iPad device.
  • Valgrind works only with iOS 5.0. iOS 6.0 is not supported.

In order to run valgrind you should:

  1. Get an Mac OS X 10.7 Lion. Fortunately, we have an old MacBook Pro with OS X 10.7 for testing purpose.
  2. Install XCode. XCode 4.5.2 works fine.
  3. Obtain a copy of valgrind‘s source from http://valgrind.org/downloads/. I took the svn version, but mac ports version works as well.
  4. I used this trick  to launch an application under valgrind in the simulator. I didn’t need to tack memory leaks, so I’m using –leack-check=no and –dsynutil=yes options.

This solution works fine for Mac OS X applications, too; however, make sure you are using Mac OS X 10.7. Looks like there is a difference in the threading model between Darwin 11 and Darwin 12; valgrind error message says:


valgrind: m_syswrap/syswrap-amd64-darwin.c:460 (void wqthread_hijack(Addr, Addr, Addr, Addr, Int, Addr)): Assertion 'VG_(is_valid_tid)(tid)' failed.

 

Share

iOS application custom localization

Apple do not recommend to allow user to switch application language another way then switch it in system settings either I do. Sometimes (rare) it makes sense to allow user to switch the language of the specific application. Anyway, before do it – please, think twice; it’s really goes against Apple’s language policy, so benefits from such a decision should overcome the drawbacks.

So, if if you want to switch your application language, use the following code:

// Use "nil" to fallback to system settings
void loadCustomLocalization(NSString * locCode) {
  NSString * appleLanguages = @"AppleLanguages";
  if (locCode) {
    [[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObject:locCode] forKey:appleLanguages];
  } else {
    [[NSUserDefaults standardUserDefaults] removeObjectForKey:appleLanguages];
  }
  [[NSUserDefaults standardUserDefaults] synchronize];
}

Call loadCustomLocalization() before UIApplicationMain() call, for example:

void loadLocalization() {
  const int result = [[[NSUserDefaults standardUserDefaults] stringForKey:@"language"] intValue];
  NSString * language = 0;
  switch (result) {
    case 1: language = @"ru"; break;
    case 2: language = @"en"; break;
  }
  loadCustomLocalization(language);
}

int main(int argc, char *argv[]) {
  loadLocalization();
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  int retVal = UIApplicationMain(argc, argv, nil, nil);
  [pool release];
  return retVal;
}

My application has three values for language setting in application settings menu.

User may choose between system language, Russian or English.

But remember: very often this is not a kind of freedom you want to give to your customers; the real freedom is to provide all application content on all possible languages (but it costs a lot).

Share

The New iPad and indexed PNG

I discovered a strange bug with indexed png on the new iPad (aka iPad 3) and I wish to share my knowledge.

The problem was the strange line at the bottom of the image on retina (@2x) image in indexed png format. Here is the graphical explanation of a problem:


The black line is not a part of image itself; it is an artifact of the New iPad drawing subsystem (CoreGraphics?). The worst thing that in the Simulator  everything looked fine, and I have no the New iPad to test on… So some time passed before I figured out what was happening. I replaced images by RGB png (thank you, ImageMagick!) and everything was OK.

To convert indexed png into RGB png install ImageMagick (via ports):

$ sudo port install imagemagick

and use script (it takes all PNGs in the current directory, converts them into RGB and also removes alpha channel by making background white):

#!/bin/bash
for f in *.png
do
filename=$(basename "$f")
extension=${filename##*.}
filename=${filename%.*}

echo converting ${filename}.$extension
mogrify "./${filename}.$extension" -type TrueColorMatte -background white -flatten +matte  -type TrueColor -define png:color-type=2 -depth 8
done
Share

Talerka – our new application is available in the App Store!

Install Talerka iOS application

I’m proud to announce the first version of Talerka application for iOS, maden with Andrey Azarov. I was glad to work with Andrey. That was a very interesting experince, and I hope that work on iPad verson will be even cooler! So enjoy Andrey’s site, dedicated to food’s recipts and install our new application. Hope you’ll like it! Note: iPad version as well as english translation will be available soon.

Share

Small investigation

I made a small investigation of iOS device and version usage.

I did not count devices older than iPhone 3G and iOS older than 3.2, but according to http://www.marco.org/2011/08/13/instapaper-ios-device-and-version-stats-update their part is lesser than 2%.

Here are the results:

iPhone devices statistic:
iPhone devices statistic

Only 6% users use iPhone 3G and 2-th generation iPod and the major part (3/4) maiden up by iPhone 4/iPhone 4S.

iOS version statistic:
iOS versions statistic

Almost 3/4 of users switched to iOS 5 as well.

So I think that the conclusion is simple: if you are not Facebook or Google and if you have no enough resources to support older iOS version, drop of the support of old devices and iOS version. Do not waste your time and efforts to support iOS older then 4.0 and to optimize your code to run smoothly on device older than iPhone 3GS. Or read this perfect article by Joel about drums and software optimization.

I picked up and accordion and do not regret.

Share

SVG on iOS

I could not find a good library for SVG for iOS:

  • SVGQuartzRenderer doesn’t compile.
  • SVGKit works good only with shipped samples and crahes on “real world” SVGs.
  • CKSVG looks good, but it doesn’t support iOS out from box as soon as it’s designed for Mac, and opens my files with some errors (anyway better than SVGKit)

The main problem is a lack of free time. I had no time to fix compilers’ error, crashes and port code for Mac to iOS. I needed a ready solution and I need it quick.

I was not alone in my research. Another guy had a same problem (and I made the same reserch as me). But I could not use unstable solution in hope that it will work in my case, so I started a little investigation and I found out, that SVG can handle touches and executes javascript. OMG it can be even animated! So I tried to put it into UIWebView and now, at 1:47 am looks like I succeed.

First, SVG can be loaded with a loadHTMLString: method; I had to use this code instead:

NSData * svg = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"africa" ofType:@"svg"]];

[webview loadData:svg MIMEType:@”image/svg+xml” textEncodingName:0 baseURL:[[NSBundle mainBundle] bundleURL]];

And SVG should looks something like that:

...
// var svgDocument; var svgDocument; var xmlns="http://www.w3.org/2000/svg" function startup(evt) { O=evt.target svgDocument=O.ownerDocument } function onclick(evt) { var t = evt.target; t.setAttribute("visibility","hidden"); i=t.getAttribute("id"); var url="map://id"; window.location = url.concat(i); } // ]]&gt;</script></p> <polygon fill-rule="evenodd" clip-rule="evenodd" points=" ... " id="egypt" onclick="onclick(evt)" /><br /> </code></p> <p>Now I can load this SVG into web view and handle an URL map://egypt in my</p> <p><code><br /> -(BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType<br /> </code></p> <p>delegate&#8217;s method!<br /> I still had to get rid of nasty black rectangle (link highlighting) when I tapping on map, but anyway, this approach is much better than rasterization my SVG images or dealing with buggy third party code&#8230;</p> <div class="addtoany_share_save_container addtoany_content_bottom"><div class="a2a_kit addtoany_list a2a_target" id="wpa2a_8"><a class="a2a_button_facebook_like addtoany_special_service" data-href="https://alxsrg.com/?p=30"></a><a class="a2a_button_twitter_tweet addtoany_special_service" data-url="https://alxsrg.com/?p=30" data-text="SVG on iOS"></a><a class="a2a_button_google_plusone addtoany_special_service" data-href="https://alxsrg.com/?p=30"></a><a href="https://www.addtoany.com/share" onclick="_gaq.push(['_trackEvent', 'outbound-article', 'https://www.addtoany.com/share', '']);" class="a2a_dd addtoany_share_save"><img src="https://alxsrg.com/wp-content/plugins/add-to-any/share_save_171_16.png" width="171" height="16" alt="Share"/></a></div></div> </div> </div> <div id="paginateIndex" class="fix"> <p><span class="left"></span> <span class="right"></span></p> </div> </div> <div class="secondaryColumn"> <div id="loginlogout-4" class="widgetContainer widget_loginlogout"> <form name='loginform' id='loginform' action='https://alxsrg.com/wp-login.php' method='post'> <label>User:</label><br /> <input type='text' name='log' id='user_login' class='input' tabindex='20' /><input type='submit' name='wp-submit' id='wp-submit' value='Login' tabindex='23' /><br /> <label>Pass:</label><br /> <input type='password' name='pwd' id='user_pass' class='input' tabindex='21' /> <span id="forgotText"><a href="https://alxsrg.com/wp-login.php?action=lostpassword" rel="nofollow" >Forgot?</a></span><br /> <a href="https://alxsrg.com/wp-login.php?action=register">Register</a> <input type='hidden' name='redirect_to' value='/?tag=ios' /> </form> <!-- WP-FB AutoConnect Button v4.2.1 --> <span class="fbLoginButton"><script type="text/javascript">//<!-- document.write('<fb:login-button scope="" v="2" size="small" onlogin="jfb_js_login_callback();">Login with Facebook</fb:login-button>'); //--></script></span></div><div id="pages-3" class="widgetContainer widget_pages"><h3 class="widgetTitle">Pages</h3> <ul> <li class="page_item page-item-2"><a href="https://alxsrg.com/?page_id=2">About me</a></li> <li class="page_item page-item-144"><a href="https://alxsrg.com/?page_id=144">Contacts</a></li> <li class="page_item page-item-187"><a href="https://alxsrg.com/?page_id=187">PGP keys</a></li> </ul> </div><div id="linkcat-29" class="widgetContainer widget_links"><h3 class="widgetTitle">Resumé</h3> <ul class='xoxo blogroll'> <li><a href="http://alxsrg.com/alexander_sergeev_resume.pdf" onclick="_gaq.push(['_trackEvent','download','http://alxsrg.com/alexander_sergeev_resume.pdf']);" >My resumé in PDF</a></li> </ul> </div> </div> <div class="secondaryColumn"> <div id="text-3" class="widgetContainer widget_text"> <div class="textwidget"><a href="http://www.adfreeblog.org/" onclick="_gaq.push(['_trackEvent', 'outbound-widget', 'http://www.adfreeblog.org/', ' ']);" target="_blank"> <img src="http://www.adfreeblog.org/adfreebutton.jpg"></a></div> </div></div></div> <div class="navStripWrapper"> <ul class="nav fix"> <li><a href="http://alxsrg.com/" title="Return to the the frontpage">Frontpage<br /><span>Return home</span></a></li> <li><a id="triggerCatID2" href="#" title="Show categories">Browse<br /><span>By topic</span></a></li> <li class="last"><a href="https://alxsrg.com/?feed=rss2" title="Subscribe to the main feed via RSS">Subscribe<br /><span>RSS feed</span></a></li> <li id="searchBar2" class="searchField"> <div> <form method="get" id="searchForm2" action="https://alxsrg.com/"> <span><input type="text" value="Search the archives..." onfocus="if (this.value == 'Search the archives...') {this.value = '';}" onblur="if (this.value == '') {this.value = 'Search the archives...';}" name="s" id="s2" /></span> </form> </div> </li> </ul> <div id="footerStrip" class="toggleCategories fix" style="display: none;"> <ul class="fix"> <li class="cat-item cat-item-1"><a href="https://alxsrg.com/?cat=1" >IT</a> </li> </ul> </div> </div> <div id="footer" class="fix"> <p class="left"><a href="http://5thirtyone.com/grid-focus" title="Grid Focus by: Derek Punsalan">Grid Focus</a> by Derek Punsalan <a href="http://5thirtyone.com">5thirtyone.com</a>.</p> <p class="right">Whitespace</p> </div> </div> <script type="text/javascript"><!-- wpa2a.targets=[ {title:"Talerka iOS 7.0 look&feel support",url:"https://alxsrg.com/?p=210"}, {title:"Dealing with AdressBook on iOS 6.0: ABAddressBookRequestAccessWithCompletion",url:"https://alxsrg.com/?p=190"}, {title:"Valgrind and iOS",url:"https://alxsrg.com/?p=180"}, {title:"iOS application custom localization",url:"https://alxsrg.com/?p=151"}, {title:"The New iPad and indexed PNG",url:"https://alxsrg.com/?p=115"}, {title:"Talerka \u2013 our new application is available in the App Store!",url:"https://alxsrg.com/?p=69"}, {title:"Small investigation",url:"https://alxsrg.com/?p=55"}, {title:"SVG on iOS",url:"https://alxsrg.com/?p=30"}]; wpa2a.html_done=true;if(wpa2a.script_ready&&!wpa2a.done)wpa2a.init();wpa2a.script_load(); //--></script> <!-- WP-FB AutoConnect Init v4.2.1 (NEW API) --> <div id="fb-root"></div> <script type="text/javascript">//<!-- window.fbAsyncInit = function() { FB.init({ appId: '121357734644477', status: true, cookie: true, xfbml: true, oauth: true, channelUrl: 'https://alxsrg.com/wp-content/plugins/wp-fb-autoconnect/assets/channel.html' }); }; (function() { var e = document.createElement('script'); e.src = document.location.protocol + '//connect.facebook.net/en_US/all.js'; e.async = true; document.getElementById('fb-root').appendChild(e); }()); //--></script> <!-- WP-FB AutoConnect Callback v4.2.1 --> <form id="wp-fb-ac-fm" name="jfb_js_login_callback_form" method="post" action="https://alxsrg.com/wp-login.php" > <input type="hidden" name="redirectTo" value="/?tag=ios" /> <input type="hidden" name="access_token" id="jfb_access_token" value="0" /> <input type="hidden" name="fbuid" id="jfb_fbuid" value="0" /> <input type="hidden" id="autoconnect_nonce" name="autoconnect_nonce" value="d1c84c5146" /><input type="hidden" name="_wp_http_referer" value="/?tag=ios" /> </form> <script type="text/javascript">//<!-- function jfb_js_login_callback() { //wpfb_add_to_js: An action to allow the user to inject additional JS to be executed before the login takes place //wpfb_add_to_js: Finished //Make sure the user logged into Facebook (didn't click "cancel" in the login prompt) FB.getLoginStatus(function(response) { if (!response.authResponse) { //Note to self: if this is happening unexpectedly, it could be because third-party browser cookies are disabled. return; } //Set the uid & access token to be sent in to our login script jQuery('#jfb_access_token').val(response.authResponse.accessToken); jQuery("#jfb_fbuid").val(response.authResponse.userID); //Submit the login and close the FB.getLoginStatus call document.jfb_js_login_callback_form.submit(); }) } //--></script> Facebook login by <a href="http://www.justin-klein.com/projects/wp-fb-autoconnect">WP-FB-AutoConnect</a><script type='text/javascript' src='https://alxsrg.com/wp-content/plugins/social-sharing-toolkit/script_2.1.2.js?ver=4.4.33'></script> <script type='text/javascript' src='https://alxsrg.com/wp-includes/js/wp-embed.min.js?ver=4.4.33'></script> <script src="https://alxsrg.com/wp-content/themes/gridfocus/js/functions.js" type="text/javascript" charset="utf-8"></script> </body> </html>