Error message

  • Notice: Undefined index: meta_name in metatags_quick_field_formatter_view() (line 374 of /var/www/www.netwalk.be/htdocs/sites/all/modules/metatags_quick/metatags_quick.module).
  • Notice: Undefined index: meta_name in metatags_quick_field_formatter_view() (line 374 of /var/www/www.netwalk.be/htdocs/sites/all/modules/metatags_quick/metatags_quick.module).
  • Notice: Undefined index: meta_name in metatags_quick_field_formatter_view() (line 374 of /var/www/www.netwalk.be/htdocs/sites/all/modules/metatags_quick/metatags_quick.module).
  • Notice: Undefined index: metatags_quick in metatags_quick_field_formatter_view() (line 375 of /var/www/www.netwalk.be/htdocs/sites/all/modules/metatags_quick/metatags_quick.module).
  • Notice: Undefined index: meta_name in metatags_quick_field_formatter_view() (line 374 of /var/www/www.netwalk.be/htdocs/sites/all/modules/metatags_quick/metatags_quick.module).

Canon Selphy CP800 and Mountain Lion

Mountain Lion and the Canon Selphy CP800 aren't best friends. Even more, the current Lion installer won't install on Mountain Lion.

When you try to install the official Lion driver for the Canon Selphy CP800 on Mountain Lion, it fails with the following error:

I've been digging around in mpkg installer, and adapted the OS version check to not check for the correct OS version anymore. You can download a copy of the adapted installer I made, or - if you don't trust my changes - you can download Canon's original driver and adapt it yourself.

Within the mpkg package, I opened Contents/distribution.dist with a text editor and changed the function installationCheck().

The check (system.compareVersions( system.version.ProductVersion, '10.8' ) != -1) needs to change into (system.compareVersions( system.version.ProductVersion, '10.8' ) == -1). After this change, the driver will install properly!

Comments welcome!

Landscape icon

Last week, I needed a landscape icon for a new iPhone app.  As I do most of the time, I check my icon library (Glyphish & co) though couldn't find a decent icon.


So I opened Illustrator and tried to make one myself. It's not fairly complex, but I'm pretty happy with the result.  In case you'd like to use it for your project, feel free to do so.  Just leave me a note saying you did.  And spread the word!

If you like the icon, let me know.  I might make some more!

Parking ~ 4411 - The saga continues...

Op vrijdag 4 mei 2012 was het eindelijk zover: Parking ~ 4411 was beschikbaar in de App Store. Feestvreugde alom: eindelijk een app waarmee je gebruik kon maken van het 4411 parkeersysteem (o.a. in Antwerpen, Brugge, Oostende, ...) zonder extra SMS kosten. De app gebruikt de 4411 web interface om parkeersessies te starten en te stoppen.

Op dinsdag 8 mei 2012 kwam de eerste donderslag. Lithic BVBA stuurde ons een email met de "vraag" waar onze GPS data vandaan kwam (i.e. de locaties van de parkeermeters). De email insinueerde dat de data onrechtmatig verkregen was, en dat we verdere stappen konden verwachten. Wij antwoordden keurig dat de data van de 4411 website komt (ze hebben een lijst van alle parkeermeters met de straatnaam waarin deze zich bevindt. Gegeven deze data is het redelijk eenvoudig om dit om te zetten naar GPS locaties.).

Op vrijdag 11 mei 2012 kwam dan de finaleslag. Belgacom NV stuurde ons een ingebrekestelling die meldde dat de app inbreuk maakte op een geregistreerd logo en naam en dat de app insinueerde een officiële Mobile-For app te zijn (hetwelk raar is - de app toont duidelijk dat ze ontwikkeld is door Netwalk, en dat wij niet gelieerd zijn aan Mobile-For noch 4411!). Na enig over en weer bellen bleek dat de naam 4411 op 11 mei 2012 geregistreerd werd (i.e. de dag van de ingebrekestelling!) Van boerenbedrog gesproken. Het logo bleek achteraf inderdaad geregistreerd, maar de zoekfunctie in het merkenbureau levert geen resultaat m.b.t. het 4411 logo. Zoeken op het registratie nummer levert dan wel het resultaat op - en inderdaad - het logo is geregistreerd. Mea culpa, inderdaad - maar niet evident om te vinden (het logo dat 4411 in de praktijk gebruikt bevat vooral de groene vlinder-man, en niet zozeer het Parkeer-WiFi symbool). We vroegen aan Belgacom (die eigenaar is van Mobile-For, hetwelk het 4411 systeem uitbaat) of het voldoende was om de app te resubmitten met gewijzigde naam (sic) en logo, maar blijkbaar volstond dit niet. De app mag niet meer in de App Store van Belgacom.

Aangezien wij geen zin (en zeker geen geld) hadden in een juridisch gevecht met de telecomreus Belgacom, besloten we op zaterdag 12 mei om 12.30 om de app offline te halen (Belgacom gaf ons 24u tijd om de app te verwijderen).

Op maandag 14 mei 2012 ging men bij Belgacom een stapje verder. De web interface van 4411 werd aangepast - met een extra random element in de login-form, waardoor de bestaande app-gebruikers niet meer konden inloggen. De gebruikers hebben nu dus een nutteloze app.

Ondertussen hebben wij niet stil gezeten. We hebben een opensource API gemaakt (beschikbaar op Github) die het via een RESTful API mogelijk maakt om in te loggen op het 4411 systeem (met de nieuwe login-form) en hebben de app aangepast om gebruik te maken van die opensource API (en dus niet rechtstreeks meer met de 4411 website). Voordeel van dit systeem is dat we de eventuele wijzigingen van 4411 direct kunnen opvangen en fixen zonder een nieuwe versie van de app te submitten. De code van de API is opensource, zodat security experts kunnen zien dat er met de login credentials niets gebeurd (i.e. ze worden enkel gebruikt om naar 4411 te sturen) en dat alle communicatie over SSL gebeurt.

De app update is reeds naar Apple gestuurd ter approval. Met wat geluk is deze volgende week geapproved, en kunnen we de update aanbieden aan de gebruikers.

 

Excuses voor alle ongemak,

Tom.

Parallax Effect

A parallax effect is, as Wikipedia describes, a displacement or difference in the apparent position of an object viewed along two different lines of sight.  Checkout some cool samples!

For a project I'm working on, I needed such an effect for their iPad app.  As I normally do, I tried looking for available components, though the only references I found were pointing towards Cocos2D (OpenGL) which seemed a bit overkill just to have the parallax effect.

So I made my own implementation, based on a few UIScrollViews. Nothing extraordinary, but nevertheless a nice effect. This is the general idea:

  • disable all but 1 UIScrollView (main)
  • define the scrollratio of the other UIScrollViews (slaves)
  • implement the main UIScrollView delegate (especially scrollViewDidScroll) and adapt the slaves' UIScrollView contentPosition's as a factor of the main's contentPosition.

You can grab a copy of my sample project to see how I implemented it.

Enjoy!

Guaranteed ASIHTTPRequests

Ever needed to use ASIHTTPRequest in combination with guaranteed delivery? (i.e. making sure the HTTP call is performed, even though the network might be down at the moment you fire the initial request)  

Here's the solution!  I've created a small extension on ASIHTTPRequest (available on Github as GuaranteedASIHTTPRequest) which implements exactly this.  Of course, for guaranteed delivery to work, you should not set a completionBlock not failureBlock (as these blocks of code cannot be stored anywhere on disk).  But if you need to fire some HTTP calls without requiring the response (e.g. report some status info to a server), this is exactly what you'll need.

You're welcome ;)

MoodClock

Our new iPad app, MoodClock has just hit the App Store.

As you can probably guess from the name it’s a combination clock and mood lamp. It doesn’t do much but what it does do, it does beautifully (well so I’d like to think). You can choose your favourite background colour (any colour you can think of) or just let it fade from one to another and you can choose black or white text and then adjust the transparency of it with a swipe of your finger.

As you can see, you can achieve an almost infinite number of effects, even turning off the time completely to use the iPad as a mood lamp – it’s surprisingly effective when turned against the wall in a darkened room – just like an expensive mood light in fact :)

The image above is a small part of a much larger promotional piece I did for the App Store (click on it to see the full version) and gives a good idea of the sort of effects you can achieve. It’s a universal app so one copy will run on both your iPad and iPhone and it’s available now.

The design of the app was done by Dave Hornsby, who we've collaborated with before, most recently with our ActionNotes app – and there’s a BIG update coming on that any day now. Watch this space…

Puzzle

For our latest project (Rupert Explores), they wanted to add a small puzzle game into the app.  As always, I started looking in Github for possible candidates, and stumbled upon SlidingPuzzleBoard, which seemed perfect for the job.  After playing with it for half an hour, I succeeded in integrating it into the app.  This is how it looks like:

Integrating the code was pretty easy.  Below's the coding I did in order to add the puzzle into Rupert Explores:

- (void)viewDidLoad
{
        [super viewDidLoad];
        if (!puzzleView)
                puzzleView = [[IAPuzzleBoardView alloc] initWithImage:[UIImage imageNamed:@"PuzzelRaket.jpg"]
                                                andSize:3
                                                withFrame:CGRectMake(388, 160, 512, 512)];
}

And the result looks astonishing. If you want to get notified whenever the puzzle is finished, you just need to implement the delegate do something with the puzzleFinished callback. This might look like this:
- (void)viewDidLoad
{
        [super viewDidLoad];
        if (!puzzleView) {
                puzzleView = [[IAPuzzleBoardView alloc] initWithImage:[UIImage imageNamed:@"PuzzelRaket.jpg"]
                                                andSize:3
                                                withFrame:CGRectMake(388, 160, 512, 512)];
                puzzleView.delegate = self;
        }
}

#pragma mark IAPuzzleBoardViewDelegate
- (void)puzzleFinished
{
        NSLog(@"Puzzle finished!");
}

Tappable URLs in Core Text

As I've been struggling to get URLs underlined and tappable in a Core Text UIView, I'd like to share my findings here.

Intro to Core Text

Drawing text using Core Text is done by following these steps:

  1. Setup a CTFramesetter for the attributed string
  2. Create CTFrame's (based on paths) which holds portions of the text
  3. Draw these CTFrame's within drawRect

Implementing these steps results in this code:

-(void)setupFrame
{
  /** 1. Setup CTFramesetter **/
  CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)myAttributedString);
  /** 2. Create CTFrame **/
  CGMutablePathRef path = CGPathCreateMutable();
  CGPathAddRect(path, NULL, CGRectMake(0, 0, 1024, 10000));            
  myFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
  CFRelease (path);
  CFRelease (framesetter);
}

/** 3. Draw CTFrame's **/
-(void)drawFrame:(CTFrameRef)frame inContext:(CGContextRef)context forString:(NSAttributedString*)as
{
  CGContextRef context = UIGraphicsGetCurrentContext();
  // This is required, otherwise the text is drawn upside down in iPhone OS (!?)
  CGContextSaveGState(context);
  CGAffineTransform flip = CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, self.frame.size.height);
  CGContextConcatCTM(context, flip);
  CTFrameDraw(myFrame, context);
}

Tappable URLs

Now, if you want to support tappable URLs, you need to do some more advanced stuff:

  1. Detect URLs within the attributed string and insert a custom attribute around each URL (whereby the custom attribute's value is the real URL)
  2. Setup a CTFramesetter for the attributed string
  3. Create CTFrame's (based on paths) which holds portions of the text
  4. Draw these CTFrame's within drawRect, line by line.  For each line, store the bounding box as well as the Range of the attributed string which is shown

The implementation might look like this:

-(void)setupFrame
{
  /** 1. Detect URLs **/
NSDataDetector* detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:&error];
NSString* string = [myAttributedString string];
[detector enumerateMatchesInString:string options:0 range:range usingBlock:^(NSTextCheckingResult* match, NSMatchingFlags flags, BOOL* stop){
  NSRange matchRange = [match range];
  [str addAttribute:(NSString*)kCTForegroundColorAttributeName value:(id)[UIColor blueColor].CGColor range:matchRange];
  [str addAttribute:(NSString*)kCTUnderlineStyleAttributeName value:[NSNumber numberWithInt:kCTUnderlineStyleSingle] range:matchRange];
  switch([match resultType])
  {
    case NSTextCheckingTypeLink:
    {
      NSURL* url = [match URL];
      [str addAttribute:@"MyURLAttribute" value:url range:matchRange];
    }
  }

  /** 2. Setup CTFramesetter **/
  CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)myAttributedString);
  /** 3. Create CTFrame **/
  CGMutablePathRef path = CGPathCreateMutable();
  CGPathAddRect(path, NULL, CGRectMake(0, 0, 1024, 10000));            
  myFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
  CFRelease (path);
  CFRelease (framesetter);
}

/** 4. Draw CTFrame's, line by line and store bounding box in variable**/
-(void)drawFrame:(CTFrameRef)frame inContext:(CGContextRef)context forString:(NSAttributedString*)as
{
  CGContextRef context = UIGraphicsGetCurrentContext();
  // This is required, otherwise the text is drawn upside down in iPhone OS (!?)
  CGContextSaveGState(context);
  CGAffineTransform flip = CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, self.frame.size.height);
  CGContextConcatCTM(context, flip);
  CGPathRef path = CTFrameGetPath(frame);
  CGRect frameBoundingBox = CGPathGetBoundingBox(path);
  CFArrayRef lines = CTFrameGetLines(frame);
  CGPoint origins[CFArrayGetCount(lines)];                              // the origins of each line at the baseline
  CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), origins);
  CFIndex linesCount = CFArrayGetCount(lines);
  for (int lineIdx = 0; lineIdx < linesCount; lineIdx++)
  {
    CGContextSetTextPosition(context, origins[lineIdx].x + frameBoundingBox.origin.x, frameBoundingBox.origin.y + origins[lineIdx].y);
    CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, lineIdx);
    CGRect lineBounds = CTLineGetImageBounds(line, context);
    lineBounds.origin.y = self.frame.size.height - origins[lineIdx].y - lineBounds.size.height;
    CFRange lineRange = CTLineGetStringRange(line);
                       
    [lineInfoForTap addObject:[NSDictionary dictionaryWithObjectsAndKeys:NSStringFromRange(NSMakeRange(lineRange.location, lineRange.length)), @"Range", NSStringFromCGRect(lineBounds), @"Bounds", nil]];
  }
}

As can be seen, this is a bit more complex.  Using the information we stored in [4], we can implement touch handling in our UIView.  Each time a touch is detected, we should check it's location, map it to a bounding box of a single line and get the range within the attributed string.  Given that range, we should loop over each character in the attributed string to check whether any character contains the custom attribute we set during [1].  If so, we fetch the value for the custom attribute (which holds the URL, see [1]) and launch the URL.

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
  [super touchesEnded:touches withEvent:event];
  CGPoint tapLocation = [[touches anyObject] locationInView:self];
  for (NSDictionary* lineInfo in lineInfoForTap)
  {
    CGRect lineBounds = CGRectFromString([lineInfo valueForKey:@"Bounds"]);
    NSRange lineRange = NSRangeFromString([lineInfo valueForKey:@"Range"]);
    if(CGRectContainsPoint(lineBounds, tapLocation))
    {
      NSRange longestRange;
      for (int i = lineRange.location; i < lineRange.location + lineRange.length; i++)
      {
        NSDictionary* attributes = [self.attributedString attributesAtIndex:i longestEffectiveRange:&longestRange inRange:NSMakeRange(i, 1)];
        NSURL* url = [attributes objectForKey:@"MyURLAttribute"];
        if (url)
        {
          [[UIApplication sharedApplication] openURL:url];
          break;
        }
      }
      break;
    }
  }
}

Hyphenation

To make things even more complex, you could add hyphenation support to your attributed string.  Core Text luckily honors UTF-8 soft-hyphen characters (0x00AD) in order to split strings but doesn't show a hard hyphenation character (-).  In order to get this working properly, you can check out Tupil's Blog. Unfortunately the hyphenation library suggested by Tupil also inserts soft hyphens for URLs which then breaks Apple's NSDataDetector class we use.  So I've made small change to NSString+Hyphenate.m, in order to not hyphenate URLs (note this change is not fool-proof but does the trick for my purposes).  Within stringByHyphenatingWithLocale in the loop which iterates over all tokens, I added this additional check:

if (tokenType & kCFStringTokenizerTokenHasNonLettersMask) {
  [result appendString:token];
}
/** begin new code **/
else if ([token isEqualToString:@"http"]) {
  while (![token isEqualToString:@" "]) {
    [result appendString:token];
    tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer);
    if (tokenType == kCFStringTokenizerTokenNone)
      break;
    tokenRange = CFStringTokenizerGetCurrentTokenRange(tokenizer);
    token = [self substringWithRange:
    NSMakeRange(tokenRange.location, tokenRange.length)];
  }
}
/** end new code **/
else {

The resulting code is now exactly what I wanted.  Core Text in combination with hyphenation support and tappable URLs.  

Netwalk becomes Mobistar Partner

Netwalk is proud to announce that as of December 1st 2012, we will be an official Mobistar partner.  Netwalk is choosen by Mobistar as premium mobile app developer which results in Netwalk being promoted by Mobistar upon customer requests for Mobile App Development.  Netwalk is dedicated to bringing high quality, state-of-the-art and unique iOS apps for SME's.  We do this either on a custom project basis, or as part of our Your-App-As-A-Service program.

For more information regarding Netwalk's services, please checkout this document.  
For more information regarding Mobistar's Partner Program, please checkout this page.

iOS PDF Reader

For a new project we're working on (a book app for children, based on Rupert the Explorer), we were looking for a PDF reading module for iPad.  After investigating the possibility to write a module ourself, we checked on Github for possible modules, and found 2 possible candidates: iOS-PDF-Reader and Reader.  We tried iOS-PDF-Reader first, as it was less bloated with overlays and allowed to easily create UIImage's from PDF pages (which was required to generate a screenshot of the front pages of the books dynamically).  After using it on an iPad 1 on a real life sample, it was just too slow to be usefull (it does not seem to rely on background processing to create lowres versions, ...).

 Rupert PDF Reader screenshot

So we switched to Reader as PDF reader, and kept iOS-PDF-Reader for generating the cover screenshots, and the result is great!  Below is the code we use to generate the screenshots (in a background thread, for performance reasons and cache them use EGOCache) as well as the code to start the PDF reader given the URL of the PDF document.  Plain easy, and a super result!

Screenshot generation


UIImage* img = [[EGOCache currentCache] imageForKey:[url lastPathComponent]];
if (!img) {
  dispatch_async(dispatch_get_global_queue(0, 0), ^{
    CPDFDocument* doc = [[CPDFDocument alloc] initWithURL:url];
    CPDFPage* page = [[CPDFPage alloc] initWithDocument:doc pageNumber:1];
    UIImage* img = [page imageWithSize:CGSizeMake(173, 128)];
    [[EGOCache currentCache] setImage:img forKey:[url lastPathComponent]];
    dispatch_async(dispatch_get_main_queue(), ^{
      [btn setImage:img forState:UIControlStateNormal];
    });
  });
}
else
  [btn setImage:img forState:UIControlStateNormal];

Launch PDF reader


ReaderDocument* doc = [[ReaderDocument alloc] initWithFilePath:[url path] password:nil];
ReaderViewController* ctrl = [[ReaderViewController alloc] initWithReaderDocument:doc];
ctrl.delegate = self;
ctrl.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController:ctrl animated:YES completion:nil];
Easy, not?



PS: For those of you wondering about memory management: since this project is iOS5 only, I rely on ARC to do my app's memory management.  And I love it!  More on ARC and Storyboarding (which is also used in this project) in a later post.

 

Pages