Archive for August, 2010

27 AugSave multiple wallpapers to Photos Album, UIImageWriteToSavedPhotosAlbum

Sure you know about method to copy an image to user photo library:

void UIImageWriteToSavedPhotosAlbum(UIImage
*image, id completionTarget, SEL completionSelector,
void *contextInfo);

but if you try to save multiple files at once

for (int i=1; i<=10; ++i){  UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);}

you’ll have every second/third file saved or even nothing will be saved. To investigate what’s wrong it’s required to set complete selector to UIImageWriteToSavedPhotosAlbum:

- (void)image:(UIImage *) image didFinishSavingWithError: (NSError *)error
contextInfo:(void *)contextInfo;

looks like this:

for (int i=1; i<=10; ++i){ UIImageWriteToSavedPhotosAlbum(image,
self, @selector(savedPhotoImage : didFinishSavingWithError:contextInfo:), nil);} -
(void) savedPhotoImage:(UIImage*)image
didFinishSavingWithError:(NSError *)error contextInfo:
(void *)contextInfo{ NSLog(@"%@",
[error localizedDescription]);}

and then you’ll see an error description “write busy” at the log. System just unable to copy files so fast. Yes, you use just a mobile device with limited, but miraculous abilities ;) It isn’t 12 Core Mac Pro.

The solution is to copy photos one by one:

  1. create an array of image names, e.g. wallpapers
  2. copy one
  3. catch complete selector
  4. goto #2

- (void) saveWallpapers{ // fill array of names wallpapers = [[NSMutableArray alloc]
 initWithCapacity:10]; for (int i=1; i<=10; ++i) {  [wallpapers addObject:imageName];
}  // start copying [self saveNextWallpaper];} - (void) saveNextWallpaper{
// copy a wallpaper and remove from the array if (wallpapers && wallpapers.count > 0)
{  UIImage *image = [UIImage imageNamed:[wallpapers lastObject]];
UIImageWriteToSavedPhotosAlbum(image,
self, @selector(savedPhotoImage:didFinishSavingWithError:contextInfo:), nil);
[wallpapers removeLastObject]; }} - (void) savedPhotoImage:(UIImage*)image
didFinishSavingWithError: (NSError *)error contextInfo: (void *)contextInfo{
// if still any wallpaper if (wallpapers)
{  [self saveNextWallpaper]if (wallpapers.count == 0)
{   // finish copying   UIAlertView  *errorAlert = [[UIAlertView alloc]
nitWithTitle:nil            message:@"Wallpapers are stored to Photos Album"
delegate:nil            cancelButtonTitle:@"ОК"
otherButtonTitles:nil];   [errorAlert show];   [errorAlert release];
[wallpapers release],
wallpapers = nil;  } }  NSLog(@"%@", [error localizedDescription]);}

25 AugTimer app supporting both iOS 4.x and iPhone OS 3.x

Updating Vipassana app with retina images and iOS 4 support I almost forget about 3.x users. Despite that months were left from iOS 4.0 release we must tranditionally support previous versions for a while. Especially if taking into account iPhone 3G perfomance on iOS 4.0.x, and generally keep silent about rarity iPhone GSM. I know several guys who still use it and love it by the way.

Now I want to show how to organize simple Timer application with both iOS 4 and iPhone OS support.

Preparations

  1. Create View-Based application
  2. Open main xib and make it looked like this

 

3. The last touch: open Target properties and chane UIKit.framework linking to Weak. It’s required when different versions of framework are used. We’ll use UILocalNotification from 4.x.
You’ll have “dyld: Symbol not found: _OBJC_CLASS_$_UILocalNotification” without it on iPad iOS 3.2, for example.
Interface
Take a look at interface and connect each IBOutlet and IBAction.

@interface TimerAppViewController : UIViewController { IBOutlet UIButton *buttonStart;
IBOutlet UIButton *buttonStop;} – (IBAction) StartTimer;- (IBAction)
StopTimer;- (void) EndTimer; – (void) cancelAlarms;- (void) scheduleAlarmAfterSeconds:
(int)sесonds; @end; @end

Implementation

This is the most difficult part. Firstly define how it should work in both ways.Oldschool:

  • user launches the app, makes some settings and presses to start a timer
  • then he has to keep the app working, because no background in iPhone OS 3.x.x
  • active display eates bettery a lot, so it’s better to turn display off. There is only one thing we could do via public API – turn on display dimming sensor and offer to user to put a device face down. It works this way, verified.
  • Timer is standard NSTimer’s method perform
  • when timer is fired then alarm notifies user

Modern style:

  • user launches an app, makes the same settings and starts the timer.
  • then he may exit the app as well as keep using in old way (keeps working and kills a battery)
  • Timer is implemented as Local Notification
  • when timer is fired then alarm appears, but:
    • if the app is inactive then user has a standard local notification (like a PUSH message)
    • if the app is active we should catch Local Notification in AppDelegate and notify user

A little confused, but rather simple in practice.

Schedule local notification (modern style) or NSTimer method perfom (oldschool):

- (void) scheduleAlarmAfterSeconds:(int)seconds{ [self cancelAlarms];
if (NSClassFromString(@”UILocalNotification”))  {   UILocalNotification* alarm
= [[[UILocalNotification alloc] init] autorelease];  if (alarm)  {
alarm.fireDate = [NSDate dateWithTimeIntervalSinceNow:seconds];   alarm.timeZone
= [NSTimeZone defaultTimeZone];   alarm.repeatInterval = 0;   alarm.alertBody = @”Time is up!”;
[[UIApplication sharedApplication] scheduleLocalNotification:alarm];
} } else  {  // schedule timers  [self performSelector:@selector(EndTimer)
withObject:nil afterDelay:seconds]; }} – (void) cancelAlarms{
if (NSClassFromString (@”UILocalNotification”))  {   UIApplication *app =
[UIApplication sharedApplication];NSArray *oldNotifications =
[app scheduledLocalNotifications];if ( [oldNotifications count] > 0)
[app cancelAllLocalNotifications];  } else {  // unschedule timers
[NSTimer cancelPreviousPerformRequestsWithTarget: self selector:@selector
(EndTimer) object:nil]; } }UI methods to start and to stop the timer, and EndTimer is called when the timer fires.

- (IBAction) StartTimer{ [self scheduleAlarmAfterSeconds:3];
[[UIApplication sharedApplication] setIdleTimerDisabled:YES]; // disable autolocking
[[UIDevice currentDevice] setProximityMonitoringEnabled:YES]; // enable screen dimming
buttonStart.enabled = NO; buttonStop.enabled = YES;} - (IBAction) StopTimer{
[[UIDevice currentDevice] setProximityMonitoringEnabled:NO];  [self cancelAlarms];
buttonStart.enabled = YES; buttonStop.enabled = NO;} - (void) EndTimer{  [self StopTimer];
[self showMessage];}

Alert notification to user:

- (void) showMessage:(NSString*)message{ UIAlertView *alert = [[UIAlertView alloc]
otherButtonTitles:nil]; [alert show]; [alert release];} - (void) showMessage
{ [self showMessage:@"Time is up!"];}  [alert release];} - (void) showMessage{
 [self showMessage: @"Time is up!"];}

In the AppDelegate we should catch a local notification and distinguish if the app is launched from background. If we catch local notification from background then user has pressed the “View” button from local notification popup message, so no additional alarm is required. Otherway the app was active and we should notify user directly calling “EndTimer“.

- (void)applicationDidEnterBackground:(UIApplication *)application
{    /* Use this method to release shared resources, save
user data, invalidate timers, and store enough application state information
to restore your application to its current state in case it is terminated later.
If your application supports background execution, called instead of application
WillTerminate:
when the user quits. */  didEnterBackground = YES;} - (void)applicationWillEnterForeground:
(UIApplication *)application
{    /* Called as part of transition from the background to the
inactive state: here you can undo many of the changes made on entering the background. */
wasEnterForeground = YES;} - (void)application:(UIApplication *)
application didReceiveLocalNotification:(UILocalNotification *)
nоtification{ if (wasEnterFоreground || didEnterBackgrоund)
{  wasEnterForeground = NO; didEnterBaсkground = NO; }
еlse { [view Сontroller EndTimer] ; didEnterBaсkgrоund = NO; }}
   Demо and Sources And traditionally (already) there is a lazy version of the article:
a video and the
roject source code.

Be effective! ;)

02 AugUser custom color theme for applications (v0.1)

The idea is to create simple and intuitive UI for user custom themes. For example, let user to define own colors of text labels and background. It’s need in some of reading applications.

Should it be interesting for all users? I don’t know. It could seem too tech/geeky, but user always has a default theme and nobody is forced to customize. Assuming not more 25% of users will need and take advantage of it. Since I’m going to integrate it in Forismatic, I’ll be able to post useful statistics some day.

What I mean a good UI is

  • intuitive – user sets a color by tapping and immediately sees a result to understand if he likes it
  • simple – user sets a color by one finger, no digit typing, quick
Color model

That’s an important quesiton, because a model defines a way of thinking. The more natural model is, the faster human adapts. “Habits are what we are.” ©

For example, try to imagine how user can think: “I want a green. A little brighter and then a little lighter, ah no.. less lighter. Yeah, that’s it, but add more saturation.”. That’s the way I search colors, others do similar way. But try to simulate it with the RGB color model. Yes, it’s hard, because it’s the way computers think. You should have experience in converting familiar logic to RGB. Most users don’t have.

Nobody said we should use RGB. Look how much models exist. I think, the most comfortable is Hue-Saturation–Brightness model (HSV:HSB). The article too long, so shortly HSV is class of models designed in the mid-1970s as the closest to human color perception. HSV is hue–saturation–value, where the last attribute may be lightness or brightness or other. For example, RGB and CMYK define any color as a combination of predefined colors, but color components in HSV model display more familiar information, like: what is a color? how much is it saturated? is it light or dark? That’s what I was searching for. Now let’s try to use it. I’ve chosen HSB for experiments. Sample applications from wikipedia:

Project

Technically all are more simply than described. We just need to change UIView.backgroundColor according to user input values. Since a color is defined by 3 components, I suggest to:

  • by tapping the screen user defines hue (H) and brightness value (B) values. The X and Y coordinates of a UITouch of the UIView.
  • by tapped moving user still changes H and B.
  • for simplicity, use UISlider to change the last component – saturation (S)
I did it this way, because I found experimentally it’s the most comfortable. You could you try it also.

Preparations

  1. use View-based Application template, name it UserColorizer.
  2. TARGETED_DEVICE_FAMILY = 1,2 (iPhone/iPad)
  3. IPHONEOS_DEPLOYMENT_TARGET = 3.0
  4. Open UIView xib (mine is Colorer.xib) and add UISlider with ValueChanged IBAction and value [0.0,1.0]; UILabel with IBOutlet.
  5. Optionally add an partially transparent UIImage. It adds variety to the interface and helps to understand if you like selected color combination.

Interface

@interface Colorer : UIViewController { IBOutlet UILabel *label;
// color components float hue; float saturation; float brightness;}
-(IBAction) slider_сhanged:(UISlider*)slider; @end
Implementation

  • set default values for components
  • update color. There is a redefined method for this: “[UIColor colorWithHue:...]
  • turn autorotation on. You should align subviews right way in Interface Builder.
  • set saturation when its UISlider changed and then update the background color

- (void) refreshColor{ self.view.backgroundColor = [UIColor colorWithHue:hue
saturation:saturation brightness:brightness alpha:1.0];} – (void)viewDidLoad {
[super viewDidLoad];  hue = 0.5; saturation = 0.5; brightness = 0.5;  [self refreshColor];}
(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{ return YES;} – (IBAction) slider_changed:(UISlider*)slider{ saturation =
slider.value; [self refreshColor];}To catch user’s tapping and moving we need to:

  • override two UIViewController’s methods
  • get touch location in the UIView
  • convert location to hue and brightness
  • update color

// return real bounds according to device position- (CGSize) bounds
{ if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
return CGSizeMake(self.view.frame.size.height, self.view.frame.size.width) ;
else  return CGSizeMake(self.view.frame.size.width, self.view.frame.size.height);}
- (void) touched:(NSSet *)touches{ CGSize size = [self bounds]; CGPoint newPoint =
[[touches anyObject] locationInView:self.view];  hue = newPoint.x/size.width; brightness
=  1-newPoint.y/size.height; [self refreshColor];} – (void) touchesBegan: (NSSet *)
touches withEvent:(UIEvent *)event{ [self touched:touches];} – (void) touchesMoved:
(NSSet *)touches withEvent: (UIEvent *) event{ [self touched:touches];}Ok, now we can change a color. Nice! It’s left to implement text coloring.

I suggest to set text color to “1.0-brightness”. Nice looking and simple to implement and use. But there is a problem area when brightness is near 0.5 value. It makes text and background with almost the same color. Again experimentaly I saw that colors are blended from value 0.45 to value 0.60 and matched the best brightness for this interval, which 0.40. It’d be nice to set a small shadow for the text, use the same color with 0.4 alpha.

- (void) refreshColor{ self.view.backgroundColor =
[UIColor colorWithHue:hue saturation:saturation brightness:
brightness alpha:1.0];  float textBrightness; if  (brightness
< 0.45 || brightness > 0.60)  textBrightness = 1.0-brightness;
else  textBrightness =0.4;  label.textColor = [UIColor colorWithHue
:hue saturation: saturation brightness:textBrightness alpha:1.0];
label.shadowColor = [UIColor colorWithHue:hue saturation:saturation
brightness: textBrightness alpha:0.4];}
Demo

That’s all what I want from the first try. Now I think about:

  • how to remove UISlider
  • more area for colors, less for dark
  • somehow to show where user tapped last time
It’s a dual sense when undestand that write an article is three times longer than to code the project. It proves Inot a writer, but a coder ;)