Showing posts with label ipad. Show all posts
Showing posts with label ipad. Show all posts

Thursday, 3 January 2013

Localizing iOS6 Storyboards with MonoTouch

Localization and internationalization of XIB and Storyboard files has historically been a very manual process. Typically these file types would be duplicated in each 'language directory' (*.lproj) and then the text and layout tweaked independently by each translator. Changes to the actual Storyboard or XIBs would need to be manually propagated across all the 'language copies'.

In iOS6 Apple introduced a new concept - Base Localization - which you can read about in their documentation on Internationalizing Your App. Your project can contain a 'special' Base.lproj directory where the Storyboard files are located, and then a corresponding *.strings file (whose filename matches the Storyboard's) in each language directory with the translations.

There is a small sample showing how this works with MonoTouch - TaskyL10nStoryboard on github. There is no IDE support currently, but you can easily create the Base.lproj directory manually in MonoDevelop and everything works as expected. Here's a screenshot of the project structure:

then inside the Storyboard itself you need to identify the controls you wish to localize. Click on a control to discover its Object ID, and shown in this screenshot:

Using the Object IDs from the Storyboard file, we can translate the display values for various properties (including text, placeholders and others) in the MainStoryboard.strings file in each language directory, like this:
"SXg-TT-IwM.placeholder" = "nombre de la tarea";
"Pqa-aa-ury.placeholder"= "otra información de tarea";
"zwR-D9-hM1.text" = "Detalles de la tarea";
"bAM-2j-Rzw.text" = "Notas";
"NF3-h8-xmR.text" = "Completo";
"MWt-Ya-pMf.normalTitle" = "Guardar";
"IGr-pR-05L.normalTitle" = "Eliminar";
Using the Object ID and property as the localization key is quite different to Apple's previous guidance on localization, where the key is typically the base-language's actual displayable value.

Here's the localized Storyboard, in Japanese:


What about layout?
Strings can often be very different lengths in different languages. When you used a different Storyboard or XIB for each language then the control sizes could be manually adjusted to fit. When using Base Localization you should use Apple's new constraint-based layout to handle these size differences.

What about iOS5 and earlier?
This method only works on iOS6. To localize text on earlier versions of iOS you will have to duplicate your Storyboards and XIBs for each language, or create outlets for all the controls and set their localized text values in code using NSBundle.MainBundle.LocalizedString().
There is another localized sample - TaskyL10n - which shows how to localize text elements directly and uses MonoTouch.Dialog.

Sunday, 23 September 2012

iOS 6 UIRefreshControl with MonoTouch

Another cute little new feature of iOS 6 is the built-in pull-to-refresh control - UIRefreshControl. It is really simple to wire up with Xamarin on a regular UITableViewController or with MonoTouch.Dialog.

In case you're unfamiliar with it, this is how the new control looks:

To implement:

  • Assign the RefreshControl property of a UITableViewController to a new instance of the control, eg. RefreshControl = new UIRefreshControl();
  • Create a handler for the control's ValueChanged event, eg RefreshControl.ValueChanged += HandleValueChanged;. In this handler your code will do whatever is required to refresh the data when the user pulls down enough to trigger the event.
  • In HandleValueChanged code, once you have refreshed the table, call the EndRefreshing method of the control to stop it spinning. Your refresh code was probably not running on the main thread, in which case you'll probably want to use InvokeOnMainThread like this: InvokeOnMainThread (() => {RefreshControl.EndRefreshing (); });

There are additional features you may use:

  • BeginRefreshing - call this method from your code if you start a refresh operation from code (eg. on a timer or in response to some other event). This sets the UI of the control to indicate there is already a refresh in progress.
  • Refreshing - whether there is already a refresh in progress.
  • AttributedTitle - optional text that appears under the refresh control.
  • TintColor - customize the appearance of the control to match your application's theme.

Refer to Apple's UIRefreshControl doc for additional info.

UPDATE: What about iOS 5?

This above example will not work on earlier versions of iOS - the UIRefreshControl class does not exist, nor does the RefreshControl property on UITableViewController. To get around this problem, the following code does a system check and falls back to an old-fashioned navbarbutton in earlier versions of iOS:

if (UIDevice.CurrentDevice.CheckSystemVersion (6,0)) {
    // UIRefreshControl iOS6
    RefreshControl = new UIRefreshControl();
    RefreshControl.ValueChanged += (sender, e) => { Refresh(); };
} else {
    // old style refresh button
    NavigationItem.SetRightBarButtonItem (new UIBarButtonItem (UIBarButtonSystemItem.Refresh), false);
    NavigationItem.RightBarButtonItem.Clicked += (sender, e) => { Refresh(); };
}

The Refresh method should contain the code that actually gets new data and updates the UITableView. That method should contain a similar if (CheckSystemVersion(6,0)) clause that wraps the call to the RefreshControl.EndRefreshing method on the main thread. Users on older operating systems will see this:

Wednesday, 19 September 2012

iOS 6 released, supported by Xamarin

It would be hard to miss the news that Apple is launching their new iPhone 5 this week, and has also released the final version of iOS 6. What's also great is that Xamarin supports iOS 6 too, on release day! There's already plenty of documentation, using C# with StoreKit, PassKit, EventKit, UIKit changes and more.

Of course there are heaps of little additions as well as those big ones, including a raft of new Core Image Filters to play with. For those working on the next Instagram (isn't everyone ;-), here's a preview of a couple of them:


(Posterize, Bloom, Invert, Perspective and Vortex)

For more information, review the Introduction to CoreImage in iOS 5 and the additional sample code for iOS 6. You might also like Apple's CoreImage Filter Reference.

Friday, 18 February 2011

Status Update: accessing Facebook with MonoTouch

Haven't blogged for a while, but hopefully that's about to change... and to kick off more regular posts here is a simple example of accessing the Facebook OpenGraph API using MonoTouch.

The core of the example is @redth's MonoTouch.Facebook.Authorization ViewController which uses the Facebook OAuth webpage hosted in a UIWebView to authenticate and grab the access_token you need for subsequent requests.

The app looks like this when it runs (and yes, when you Save your status update, it appears directly on your Facebook Profile):
The code is available on github at github.com/conceptdev/Facebook - remember you need to have a Facebook Application 'Client ID' (create one here) to get it working. NOTE: this is a sample - there is very little in the way of error detection, correction or recovery... left as an exercise for the reader :)

Facebook Login
The Facebook OAuth 'magic' happens inside a UIWebView... we create one and navigate to https://graph.facebook.com/oauth/authorize which displays and processes the login form. A successful authentication results in a redirect to a 'known page'; the redirected URL contains the access_token needed for subsequent 'authorised' requests so the code strips it out and saves it. Thanks again to Jon without whom the rest of the code wouldn't be here!

Facebook Newsfeed
Getting the news feed is relatively simple and thanks to some useful info on using Json.NET with MonoTouch it is easy to parse too!
var b = wc.DownloadData(
   new Uri("https://graph.facebook.com/me/home?access_token=" + token));
var s = Encoding.UTF8.GetString(b);
var posts = JsonConvert.DeserializeObject<Posts>(s);
The parsing works so easily because Objects.cs contains a set of classes that (roughly, not completely) match the JSON returned by Facebook, for example:
{"data":[\{"id":"57755305836_10150135900140837","from":\{"name":"Tiga","category":"Musician\\/band","id":"57755305836"\},
"message":"Bad DJ Signs Vol.1:  when your dancefloor reminds you of the \\"club\\" scene from \\"Vanilla Sky\\"",
"icon":"http:\\/\\/photos-d.ak.fbcdn.net\\/photos-ak-snc1\\/v27562\\/23\\/2231777543\\/app_2_2231777543_9553.gif",
"actions":[\{"name":"Comment","link":"http:\\/\\/www.facebook.com\\/57755305836\\/posts\\/10150135900140837"\},
\{"name":"Like","link":"http:\\/\\/www.facebook.com\\/57755305836\\/posts\\/10150135900140837"\},
\{"name":"\\u0040ciaotiga on Twitter","link":"http:\\/\\/twitter.com\\/ciaotiga?"\}],
"type":"status","created_time":"2011-02-18T07:23:42+0000","updated_time":"2011-02-18T08:25:22+0000",
"likes":33
maps to
The DeserializeObject() call is then all that is required to take the JSON string and turn it into an object graph that is easily bound to a UITableView (see Main.cs).

Facebook Status Update
Once we have the authentication token updating your status is easy - a simple POST using WebClient is all it takes.
System.Net.WebClient wc = new System.Net.WebClient();
var result = wc.UploadString
("https://graph.facebook.com/me/feed?access_token=" + token
, "message=" + status);


but isn't there a Facebook API for iOS?
Yes, in addition to this 'manual' way of authenticating using an embedded webpage Facebook also provides a Mobile API. The UI presented to the user looks nicer than the webpage AND it can interact with the official Facebook app (if installed). To get that working with MonoTouch requires btouch-library bindings... but that's a story for another post.

UPDATE: (from comments below) @klmcmahon has already ported the Facebook iOS SDK and sample to MonoTouch - thanks Kevin!

Wednesday, 4 August 2010

UIGlassButton Generator in MonoTouch

Sometimes the default look of iOS controls (such as UIButton) is a little 'flat'. Apple themselves sometimes use a very pretty 'glass button' effect, which could be achieved (apparently, I'm paraphrasing here) using an undocumented UIGlassButton class. You can read more about this in the source links for this sample:
Anyway... Martin did a great job porting this approach to MonoTouch, creating a GlassButton.dll wrapper as part of a sample project that allows you to create great-looking PNG-image-glass-buttons to incorporate into your iOS projects. The only thing missing was a double-resolution version for my new iPhone4's Retina Display, so that's what I've added to this github project: GlassButtonGenerator.

How it works:
  1. Edit the string constants for button text and filename
  2. Edit the filename which is used in the path to save the output
  3. Run the app in the Simulator and press the [Generate Glass Button] button
  4. Grab the images that are saved to your Desktop
  5. Use the images in your iOS app in UIButton controls
Here is the 'output' when you are running it in the simulator (the button images should be saved to your Desktop):

If you are then wondering what to do with the two images, check out Miguel's post on Building apps for the Retina Display. Basically you should use UIImage.FromBundle to load the image and ensure that the 'regular' and 'retina' versions have the same filename (with the 'retina' version having a suffix @2x).

For the code below, the /Images/ folder has four images: BuyGlass.png, BuyGlass@2x.png, CancelGlass.png, CancelGlass@2x.png (where the @2x images have double the height & width dimensions of the base image)...
buyButton.SetImage(
     UIImage.FromBundle("Images/BuyGlass.png")
   , UIControlState.Normal);
cancelButton.SetImage(
     UIImage.FromBundle("Images/CancelGlass.png")
   , UIControlState.Normal);

Don't forget to make the Build Action: Content for your image files! And remember, DON'T use UIGlassButton in your apps directly - generate the images and include them.

UPDATE (May 2011): Miguel has posted a code-based glass button implementation which you might prefer to this pre-generated image one. See Glass button for iPhone and the code on github.

Friday, 23 July 2010

Drawing on Maps with MonoTouch

iOS4 introduced new features like MKOverlay to help draw lines/routes and shapes/polygons on the MKMapView control, however it has always been possible to add these features to maps in iOS3.

Two (Objective-C) examples of displaying geometric shapes on MKMapView in iOS3 are:
...it is these examples that I've ported to MonoTouch (as a Universal app: iPhone and iPad). You can grab the code from github project MapStuff and see some screenshots below:

I put together a class diagram to try and explain how they work (the classes shown are from the 2nd example, which allows you to draw your own shape).

Please remember all the hard work here has been done by the original authors. All credit goes to them. Any bugs in the MonoTouch code are mine... let me know if you find any.

iOS4 examples to follow...

Sunday, 18 July 2010

More MonoTouch Machine-translation Madness!

In addition to the recent dodgy Spanish localization, my TweetStation fork now has dodgy French, Japanese, German and Italian translations (courtesy of the Microsoft Translator API that was introduced at MIX10). Here's how they look:



The following two code snippets were added to ngenstrings so that once the text has been extracted from TweetStation, it is automatically translated and written to .strings files ready for inclusion in the correctly-named .lproj folders (this code hasn't been committed to github - but it should be easy to add to your local copy).

add code to the end of MainClass.Main
string[] languages = new string[]{"fr","ja","it","de"};
foreach (var language in languages)
{
   foreach (var table in tables.Values)
   {
      foreach (LocalizedString locstring in table.Values)
      {
         locstring.Value = Translate(locstring.Key, language);
      }
      table.WriteStringsFile(assemblyName, outputFormat, language);
   }
}

add method to MainClass
//http://msdn.microsoft.com/en-us/library/ff512421.aspx
static string Translate (string text, string toLanguageCode)
{
   string appId = "REGISTER_AND_INSERT_YOUR_APPID";
   string translation = text;
   text = text.Replace(" ", "%20").Replace("&", "").Replace("#","%3F");

   string detectUri = "http://api.microsofttranslator.com/v2/Http.svc/Translate?appId=" + appId +
  "&text=" + text + "&from=en&to=" + toLanguageCode;
   Console.WriteLine(detectUri);
   try {
      System.Net.HttpWebRequest httpWebRequest = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(detectUri);
      System.Net.WebResponse resp = httpWebRequest.GetResponse();
      Stream strm = resp.GetResponseStream();
      StreamReader reader = new System.IO.StreamReader(strm);
      translation = reader.ReadToEnd();
   }
   catch (Exception e)
   {
      Console.Write("Translation failed for " + text + " - " + e.Message);
   }
   return translation;
}

Hopefully that gives you some ideas (or inspiration) on how to increase the market for your apps across the world. Remember that machine-translation is NOT a substitute for a professional human translation, however it can be useful to test your application's ability to handle and display different languages (eg. the size of the strings, etc).

Friday, 16 July 2010

TwitEstaƧion: MonoTouch TweetStation en espaƱol

If you are a MonoTouch developer then you should already be familiar with the two subjects of this post:
It just so happens that Chapter 12 - Localizing for an International Audience of the book introduces a small utility called ngenstrings that helps you to translate your application into other languages. Today I updated ngenstrings to use the latest Mono.Cecil version, and to test it out I decided to have a go at localizing TweetStation into Spanish. You can download the Localizable.strings file that was created (and translated courtesy of Bing). Here's a screenshot showing where it fits into the solution:
...and here are a couple of screenshots showing TwitEstaƧion in action ;-)

NOTE: Miguel had already made a good start building an internationalized app - many of his strings were wrapped in a Locale.GetText() method which was easy for me to work with. None of his code needed to change except for Locale.Get() and Locale.Format() to add NSBundle.MainBundle.LocalizedString(str,"");
NOTE2: this was a MACHINE TRANSLATION - of course you would arrange for a real person to translate your apps before taking them to the AppStore!
NOTE3: I have not translated 100% of Miguel's app - this is for demonstration purposes at the moment.
NOTE4: It's called TwitEstaƧion because "TweetEstaƧion" is too long for the iPhone home screen, and gets shortened unreadably. I don't speak Spanish so who knows if that makes any sense at all!

UPDATE: a few more screenshots... (again, apologies for the machine translation:)
UPDATE2: localized code forked from TweetStation main now available on github.

Friday, 9 July 2010

MonoTouch port of TDBadgedCell

I recently wanted to add an 'item count' to a UITableView in one of my iPhone/iPad projects and thought it would be nice to match the grey/encircled 'badge' used in the Mail application.

Happily,  I found (via The Unofficial Apple Weblog) that Tim Davies had the same idea and already done the hard work, albeit in Objective-C. Tim's code for TDBadgedCell is on github (you can help him out and donate, too), as is the MonoTouch/C# port: TDBadgedCellSharp. Both produce the same output (shown below)

The important stuff (TDBadgeView and TDBadgeCell) is in TDBadgedCell.cs - the rest of the project is purely for demonstration purposes.

Here's a very simple example of how to use the badged cell (warning: doesn't show DequeueReusableCell which you would normally use in the GetCell method)...

public override UITableViewCell GetCell 
   (UITableView tableView, NSIndexPath indexPath)
{
   TDBadgedCell cell = new TDBadgedCell (UITableViewCellStyle.Subtitle, "Cell");
   cell.TextLabel.Text = contents[indexPath.Row].Title;
   cell.TextLabel.Font = UIFont.BoldSystemFontOfSize (14);

   cell.DetailTextLabel.Text = contents[indexPath.Row].Detail;
   cell.DetailTextLabel.Font = UIFont.SystemFontOfSize (13);

   cell.Accessory = UITableViewCellAccessory.DisclosureIndicator;
   cell.BadgeNumber = Convert.ToInt32 (contents[indexPath.Row].Badge);

   if (indexPath.Row == 1)
      cell.BadgeColor = UIColor.FromRGBA (1.000f, 0.397f, 0.419f, 1.000f);
   if (indexPath.Row == 2)
      cell.BadgeColor = UIColor.FromWhiteAlpha (0.783f, 1.000f);
   return cell;
}

Thanks Tim!

..and finally, if you didn't notice the cover at the top of this page or seen the tweets, "my" book (and Wally, Chris, Martin & Rory's :) is now available from Amazon (and other locations, in paper or PDF)!

Monday, 3 May 2010

iPad 'infinite scrolling' with MonoTouch

Tonights exercise proves that sometimes it's worth taking time out to 'design' a chunk of code before writing it. Here's the "design"... can you see what it's trying to do?


Okay, I'll tell you: I'm trying to create an 'infinite' (well, just a configurable number) of scrollable pages that are rendered with UIWebView controls. I want to use the minimum number of controls for performance and memory but I also want a nice scrolling experience where the next and previous pages are aways pre-loaded. As you scroll left and right, the UIWebViews are shuffled around (X-coordinates only) to create an effect similar to the Time magazine 'app'.

Amazingly it worked out pretty quickly - here are a couple of screens to "prove" that it scrolls left & right as expected. It's using a "CSS3" multi-column hack in the Html, but that's another story...


And here are the 'important bits' of the code. I'm not publishing the entire source right now - the app still has a long way to go - but you get the idea... I hope. I only had a brief look around for existing examples so if you've seen a better one, let me know!

// declarations
UIScrollView MyScrollView;
UIWebView[] WebViewArray = new UIWebView[3];
int[] WebViewHasPage = new int[]{0,1,2};
int NumberOfPages = 8; // news01.html .. news08.html
...
// ViewController ctor
var bounds = new RectangleF(0, 0, View.Bounds.Width, View.Bounds.Height);
MyScrollView = new UIScrollView(bounds);
RectangleF scrollFrame = MyScrollView.Frame; // start with screen size
scrollFrame.Width = PageWidth * NumberOfPages; // then make the content
MyScrollView.ContentSize = scrollFrame.Size; // 8 screens wide
MyScrollView.Scrolled += ScrollViewScrolled; // and attach event handler
for (int i = 0; i < 3; i++)
{ // lay out three web controls to shuffle around
webViewFrame.X = PageWidth * i;
WebViewArray[i] = new UIWebView(webViewFrame);
WebViewArray[i].ScalesPageToFit = false;
var u = NSUrl.FromFilename(basedir+"news0"+(i+1)+".html");
var r = new NSUrlRequest(u);
WebViewArray[i].LoadRequest(r);
MyScrollView.AddSubview (WebViewArray[i]);
}
...
// and the method that does the shuffling
private void ScrollViewScrolled (object sender, EventArgs e)
{
UIScrollView sv = (UIScrollView)sender;
double page = Math.Floor ((sv.ContentOffset.X - sv.Frame.Width / 2) / sv.Frame.Width) + 1;
int LastPage = CurrentPage;
int NewPage = CurrentPage;
if (sv.ContentOffset.X % PageWidth == 0) NewPage = (int)page; // moved pages!
if (NewPage >= 0 && NewPage < NumberOfPages)
{
pageControl.CurrentPage = NewPage;
CurrentPage = NewPage;
int updatePage = 0;
bool shouldMove = false;
if (NewPage > LastPage)
{// moving right
updatePage = NewPage + 1; shouldMove = true;
}
else if (NewPage < LastPage)
{// moving left
updatePage = CurrentPage - 1; shouldMove = true;
}
if (shouldMove & (updatePage >= 0) & (updatePage < NumberOfPages))
{
int webViewSlot = updatePage % 3;
if (WebViewHasPage[webViewSlot] == updatePage) return; // already has it
else WebViewHasPage[webViewSlot] = updatePage;
var url = NSUrl.FromFilename(BaseDirectory+"news0"+(updatePage+1)+".html");
var request = new NSUrlRequest(url);
WebViewArray[webViewSlot].LoadRequest(request);
var rect = new RectangleF (
WebViewArray[0].Frame.Width * updatePage
, WebViewArray[webViewSlot].Frame.Y
, WebViewArray[webViewSlot].Frame.Width
, WebViewArray[webViewSlot].Frame.Height);
WebViewArray[webViewSlot].Frame = rect;
}
}
else
{
Console.WriteLine ("Scrolled a little too far, to page " + page);
}
}