Adding a UIPopover to UISlider

slider_cupcakes

Overview

If you have an iPad you have probably used iBooks, Apple’s eBook application that gives users access to the iBooks store. In this application you can navigate through books in a number of ways. Today we are going to focus on the scroll bar at the bottom of a book that a user can utilize to skip to any given page within the book. This control involves a customized UISlider and a UIPopoverView that drags along with the slider as the value changes. Today we will be making a UISlider subclass that will duplicate this functionality.

How to Use

ELCSlider Demo from Collin Ruffenach on Vimeo.

So that is it! Put a UISlider in your XIB and then change its class to ELCSider. You can change the range of the slider through interface builder and the slider will work the same way. If you would like to change the appearance of the pop over, the number of digits that are shown or anything else you will need to change a bit of code. Look below for how the magic of this subclass is done.

GitHub

You can find this project now on GitHub. Please let me know any issues you may have. Happy coding!

How its Made

The ELCSlider package is a collection of 5 files. 2 classes and 1 XIB. The first class that we will look at is the ELCSlider class which is a UISlider subclass. First taking a look at the header, we declare a class property PopoverController and SliderValueViewController. You can see the code below.



#import "SliderValueViewController.h"

@interface ELCSlider : UISlider {

	UIPopoverController *popoverController;
	SliderValueViewController *sliderValueController;
}

@end

With the simple header declared we can go into the main of the ELCSlider. The first method you see in the main is the initWithCoder: method. This gets us into an interesting part of the iOS SDK that I we should investigate quickly. Looking at where initWithCoder: is declared, we see that it is a part of the NSCoding Protocol. This method is passed an archived object and is essentially to take saved files and turn them back into Objective C objects. If you ever create a custom class, and you want to save an instance of that class to a file, you will need to have that class conform to the NSCoding Protocol. If we look down the inheritance chain of a UISlider we will see that UIView indeed conforms to this protocol. This is because all UIView’s are going to need to archive and unarchive themselves from a .XIB file. As a result of this, when you lay out an element in a XIB the initialization method that will be called will be initWithCoder. Within the method we ensure that our super class initialized properly, and then we proceed to assign the method valueChanged: to the control event UIControlEventValueChanged. This will make a call to the valueChanged method to let us handle moving the UIPopoverView around based on the current value of the slider. After that we create an instance of our SliderValueViewController, which we will go through in the next section, and create a UIPopoverController with the content view controller being our SliderValueViewController. You can see the code for our initialization method below.

-(id)initWithCoder:(NSCoder *)aDecoder {
	if(self = [super initWithCoder:aDecoder]) {

		[self addTarget:self action:@selector(valueChanged:) forControlEvents:UIControlEventValueChanged];

		sliderValueController = [[SliderValueViewController alloc] initWithNibName:@"SliderValueViewController" bundle:[NSBundle mainBundle]];
		popoverController = [[UIPopoverController alloc] initWithContentViewController:sliderValueController];
		[popoverController setPopoverContentSize:sliderValueController.view.frame.size];
	}

    return self;
}

The only remaining method to make is the method to handle what happens when the slider value changes. This method is all about doing the math to calculate the CGRect where we want out UIPopoverView to show itself. This is a value that will be derived based on the x origin of the slider, the slider width, the slider’s max and min, the current value and the size of the slider circular button (22px). I wrote this class so that it supports any type of slider range, positive to positive, negative to positive and negative to negative. The math is kind complex to figure out exactly where to display the pop over, but you can take a look at the source if you are curious. The real trick was measuring the width of the UISlider circular button as the center of it doesn’t go all the way to the edge of the slider, you you need to add or subtract some small amount to the x coordinate you compute depending on the value the slider is reporting. You can see the method below.

-(void)valueChanged {

	[sliderValueController updateSliderValueTo:self.value];

	CGFloat sliderMin =  self.minimumValue;
	CGFloat sliderMax = self.maximumValue;
	CGFloat sliderMaxMinDiff = sliderMax - sliderMin;
	CGFloat sliderValue = self.value;

	if(sliderMin  halfMax) {

		sliderValue = (sliderValue - halfMax)+(sliderMin*1.0);
		sliderValue = sliderValue/halfMax;
		sliderValue = sliderValue*11.0;

		xCoord = xCoord - sliderValue;
	}

	else if(sliderValue <  halfMax) {

		sliderValue = (halfMax - sliderValue)+(sliderMin*1.0);
		sliderValue = sliderValue/halfMax;
		sliderValue = sliderValue*11.0;

		xCoord = xCoord + sliderValue;
	}

	[popoverController presentPopoverFromRect:CGRectMake(xCoord, 0, sliderValueController.view.frame.size.width, sliderValueController.view.frame.size.height) inView:self permittedArrowDirections:UIPopoverArrowDirectionDown animated:YES];
}

Creating the Popover’s Content View Controller

Now that we have all of the logic in place to move the view controller appropriately, we need to create the view controller that will be injected into the Popover. The view controller I have created is meant to be restyled, refactored or even replaced. The view controller has one required method, updateSliderValueTo:. This method is called every time the slider value changes. People may use this method to change other aspects of the content view controller, such as background color, or possibly other labels. Please feel free to customize as much as you like.

GitHub

You can find this project now on GitHub. Please let me know any issues you may have. Happy coding!

Follow me on twitter @cruffenach

Working with UIGestureRecognizers

apple-multi-touch-gesture-language

Hey iCoders! Today we are going to make a fun project that takes advantage of UIGestureRecognizers which were introduced in iOS 3.0, way back when it was called iPhone OS. UIGestureRecognizer is an abstract class that several concrete classes extend Eg. UITapGestureRecognizer, UIPinchGestureRecognizer. Today we are going to be building a simple photo board application. You will be able to add photos from your board, move, rotate and zoom them in and out around the board. We will also build in some simple physics to give a sense of the photos being thrown around the board. Here is a short video of what our final product will look like.

Demo of Photo Board Application for iCodeBlog.com from Collin Ruffenach on Vimeo.

GIT Hub

You can find this project now on GitHub. Please let me know any issues you may have. Happy coding!

Creating the project

Lets get a project ready that can handle all of this functionality. Open up xCode and start a View based iPad application called DemoPhotoBoard. Once the project window has come up go to the Framework group in the left bar and right click on it. Select Add -> Existing Framework… A large model view will come down listing all of the Frameworks that can be added to the project. Add in the Framework “Mobile Core Services”. Now go into DemoPhotoBoardViewController.h and add in the line

I apologies for having to use the image there, WordPress hates any line of code with < or > in it. We might as well finish filling in the rest of the header now too. Don’t worry about what these properties will be used for yet just include them in the header for the moment, they will be what we use to keep track of our scaling, movement and rotation. The add photo method will be called from a button we put in our interface in the next step.

@interface DemoPhotoBoardViewController : UIViewController  {

	CGFloat lastScale;
	CGFloat lastRotation;

	CGFloat firstX;
	CGFloat firstY;
}

-(IBAction)addPhoto:(id)sender;

@end

Filling in the XIB

The next thing we are going to do is add a tool bar and tool bar button to our XIB. Double click on DemoPhotoBoardViewController.xib. Once it has opened drag in a UIToolBar Item and then Put a UIToolBarButtonItem with a Flexible Space element to the left of it. Make the UIBarButtonItem the system button “Add”. Now if you go to the file owner below and right click on it, you should  see an outlet for the method “addPhoto”. Connect this to the add button we have. As a final step, select the UIToolBar and look at its size panel in the inspector. Make sure the Autosizing settings match the settings seen below so that things don’t get screwy when the app is in other orientations.

Implementing the Photo Picker

Go ahead and open up DemoPhotoBoardViewController.m. The first thing we are going to do is implement the addPhoto method. Insert the following code into your main.

-(IBAction)addPhoto:(id)sender {

	UIImagePickerController *controller = [[UIImagePickerController alloc] init];
	[controller setMediaTypes:[NSArray arrayWithObject:kUTTypeImage]];
	[controller setDelegate:self];

	UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:controller];
	[popover setDelegate:self];
	[popover presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
}

This method creates a UIImagePickerController and tells it to only display images. Next we create an UIPopoverController that we instantiate with our UIImagePickerController as the content view controller. We set the delegate to ourself, and present it from the bar button item sender, which refers to the add button in our interface. We know the pop over will always be below our button so we force the button direction to always be point up. With this done, we can now run the app and see a UIImagePickercController appearing in a UIPopOverController below our add button.

Setting up the Gesture Recognizers

Now we need to implement the delegate method for our UIImagePickerController and add the image to our view when it is selected. We do this with the imagePickerControler:DidFinishPickingMediaWithInfo: delegate method. This method will provide us a dictionary where the key @”UIImagePickerControllerOriginalImage” will return a UIImage object of the image the user selected. We are going to need to create an ImageView and then put this ImageView in a UIView holder. The reason we do this is because standard UIImageViews, despite being UIView subclasses, do not react to gesture recognizer added to them. I’m not exactly sure why that is but this is the solution I have found in my testing. We are going to create 4 different kinds UIGestureRecognizers and connect them to our holder view.

We will first create a UIPinchGestureRecognizer. This object doesn’t require and customization, we will simply set its target to us with the scale: selector and assign this class as its delegate. With this done we add it to the holder view we created.

Next we create a UIRotationGestureRecognizer. This object doesn’t require much customization either. We simply set it to call the rotate: method in our class and set its delegate.

Net we create the UIPanGestureRecognizer. We create the PanGestureRecognizer to make a call to the method move: upon being activated. We tell the PanGestureRecognizer that we only care when a single touch is panning by setting the maximum and minimum touches to 1. We once again add this to the holder view we created.

The final UIGestureRecognizer we create is the UITapGestureRecognizer. The UITapGestureRecognizer will be used to stop an object that has been “thrown” from going all the way to its stopping point. It essentially will be used to catch an object while it is still moving. We set the required number of taps to 1 and set the delegate. We add this final UIGestureRecognizer to our holder view and add the view to subview. You can see the code below. Please ignore the random number WordPress is weak sauce.


- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {

	UIImage *image = [info objectForKey:@"UIImagePickerControllerOriginalImage"];

	UIView *holderView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, image.size.width, image.size.height)];
	UIImageView *imageview = [[UIImageView alloc] initWithFrame:[holderView frame]];
	[imageview setImage:image];
	[holderView addSubview:imageview];

	UIPinchGestureRecognizer *pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(scale:)];
	[pinchRecognizer setDelegate:self];
	[holderView addGestureRecognizer:pinchRecognizer];

	UIRotationGestureRecognizer *rotationRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotate:)];
	[rotationRecognizer setDelegate:self];
	[holderView addGestureRecognizer:rotationRecognizer];

	UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(move:)];
	[panRecognizer setMinimumNumberOfTouches:1];
	[panRecognizer setMaximumNumberOfTouches:1];
	[panRecognizer setDelegate:self];
	[holderView addGestureRecognizer:panRecognizer];

	UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)];
	[tapRecognizer setNumberOfTapsRequired:1];
	[tapRecognizer setDelegate:self];
	[holderView addGestureRecognizer:tapRecognizer];

	[self.view addSubview:holderView];
}

Quickly lets also define each of these methods as such so we can see them all occurring as we touch an object that we add to the view. Add this code in and run the application, you can click around on the objects you add to the board and see the Log messages displaying in the terminal. In the terminal you may or may not be able to activate all of these, because of multi touch being simulated in a pretty limited way. But you can run the code and try this out.

-(void)scale:(id)sender {
	NSLog(@"See a pinch gesture");
}

-(void)rotate:(id)sender {
	NSLog(@"See a rotate gesture");
}

-(void)move:(id)sender {
	NSLog(@"See a move gesture");
}

-(void)tapped:(id)sender {
	NSLog(@"See a tap gesture");
}

UIGestureRecognizer Action Methods

All UIGestureRecognizers contain a state property of type UIGestureRecognizerState. This is because of UIGestureRecognizers calling their action methods throughout the entire time a gesture is being performed. When the gesture first begins the state of the calling UIGestureRecognizer is UIGestureRecognizerStateBegan, throughout its all subsequent calls have the state UIGestureRecognizerStateChanged, and the final call is of state UIGestureRecognizerStateEnded. We can use this to our advantage to do house keeping in each of our gesture action methods. Another important thing to note about the action calls from UIGestureRecognizers is that the properties it will report about a gesture, such as scale for UIPinchGestureRecognizer and rotation for UIRotationGestureRecognizer, will always be in reference to the original state of the object. So as a scale is happening the scale may be reported as; 1.1, 1.2, 1.3, 1.4 and 1.5 on subsequent calls. These scales are not cumulative but all in reference to the original state of the object the UIGestureRecognizers are attached to.

Implementing Scaling

First thing we will do is implement the scale method. The scale method will be called by an id sender, this sender will actually be a UIPinchGestureRecognizer object. If we look at the documentation for a UIPinchGestureRecognizer object we will see that it includes a scale property that is a CGFloat. This scale property will be provided by the UIPinchGestureRecognizer every time the scale: method is called. Because the scale will be cumulative and always in reference to the original state of the object. Because of this, as we make our photo grow, we must make sure that we only scale by the difference of the last scale to the current scale. For example of the first scale: call has the UIPinchGestureRecognizer scale as being 1.1 and the next call has it by 1.2, we should scale by 1.1 and then by another 1.1. To handle this we have the the class property CGFloat lastScale. This will keep track of the last scale we applied to our view so  that on the next call we can only apply the difference between them.

So now that we can tell how much to scale an item upon being pinched we need to look into the mechanism that will actually scale the view. Every UIView has a CGAffineTransform property called transform. This described much of the geometry of the view that will be drawn. Using method provided by the Quartz frameworks we will figure out how to change this variable to scale as we need. Lets first take a look at out whole scaling method.

-(void)scale:(id)sender {

	[self.view bringSubviewToFront:[(UIPinchGestureRecognizer*)sender view]];

	if([(UIPinchGestureRecognizer*)sender state] == UIGestureRecognizerStateEnded) {

		lastScale = 1.0;
		return;
	}

	CGFloat scale = 1.0 - (lastScale - [(UIPinchGestureRecognizer*)sender scale]);

	CGAffineTransform currentTransform = [(UIPinchGestureRecognizer*)sender view].transform;
	CGAffineTransform newTransform = CGAffineTransformScale(currentTransform, scale, scale);

	[[(UIPinchGestureRecognizer*)sender view] setTransform:newTransform];

	lastScale = [(UIPinchGestureRecognizer*)sender scale];
}

The first thing we do in this method is bring the touched view to the front. We do this by accessing the view property of our sender which in this case is the UIPinchGesturereRecognizer. Next thing we do is check if this is the final touch in this pinch motion. If it is we reset out lastTouch value to 1. When the scale of one is applied a view does not change. When a pinch has ended we set the final pinch as being the new starting point for the next pinch sequence, specifically 1. Any other touch besides the last will subtract the difference from the last scale and the current scale from 1. This will be the scale change between the current touch and the last. We want to apply this to the current CGAffineTransfom matrix of the view this gesture recognizer is attached to. We now get the current transform of the view and pass it into CGAffineTransformScale() method. The first parameter is for current transform and the following two are the x and y scale to be applied to the passed in transform. The output here will be the new transform for the view. We apply this and reset the scale.

Implementing Rotation

Next thing we handle is rotation. This method has a very similar structure to the scaling method. We use another class property called lastRotation this time instead and a slightly different Quartz method, but the code overall should make sense. Check it out below.

-(void)rotate:(id)sender {

	[self.view bringSubviewToFront:[(UIRotationGestureRecognizer*)sender view]];

	if([(UIRotationGestureRecognizer*)sender state] == UIGestureRecognizerStateEnded) {

		lastRotation = 0.0;
		return;
	}

	CGFloat rotation = 0.0 - (lastRotation - [(UIRotationGestureRecognizer*)sender rotation]);

	CGAffineTransform currentTransform = [(UIPinchGestureRecognizer*)sender view].transform;
	CGAffineTransform newTransform = CGAffineTransformRotate(currentTransform,rotation);

	[[(UIRotationGestureRecognizer*)sender view] setTransform:newTransform];

	lastRotation = [(UIRotationGestureRecognizer*)sender rotation];
}

Implementing Movement

Now we handle movement which is a bit different that the rotation and scaling transformations. Although you could use the transform to move an object around using the transform property, we are going to continuously reset the center of each view. We utilize PanGestureRecognizers method translationInView to get the point which the view has been moved to in reference to its starting point. If this is the first touch from the gesture recognizer we set our class properties firstX and firstY. We the calculate our translated point by adding the original center points to the translated point in the view. We set the view’s center with this newly calculated view. You can see the code below.

-(void)move:(id)sender {

	[[[(UITapGestureRecognizer*)sender view] layer] removeAllAnimations];

	[self.view bringSubviewToFront:[(UIPanGestureRecognizer*)sender view]];
	CGPoint translatedPoint = [(UIPanGestureRecognizer*)sender translationInView:self.view];

	if([(UIPanGestureRecognizer*)sender state] == UIGestureRecognizerStateBegan) {

		firstX = [[sender view] center].x;
		firstY = [[sender view] center].y;
	}

	translatedPoint = CGPointMake(firstX+translatedPoint.x, firstY+translatedPoint.y);

	[[sender view] setCenter:translatedPoint];

	if([(UIPanGestureRecognizer*)sender state] == UIGestureRecognizerStateEnded) {

		CGFloat finalX = translatedPoint.x + (.35*[(UIPanGestureRecognizer*)sender velocityInView:self.view].x);
		CGFloat finalY = translatedPoint.y + (.35*[(UIPanGestureRecognizer*)sender velocityInView:self.view].y);

		if(UIDeviceOrientationIsPortrait([[UIDevice currentDevice] orientation])) {

			if(finalX < 0) { 				 				finalX = 0; 			} 			 			else if(finalX > 768) {

				finalX = 768;
			}

			if(finalY < 0) { 				 				finalY = 0; 			} 			 			else if(finalY > 1024) {

				finalY = 1024;
			}
		}

		else {

			if(finalX < 0) { 				 				finalX = 0; 			} 			 			else if(finalX > 1024) {

				finalX = 768;
			}

			if(finalY < 0) { 				 				finalY = 0; 			} 			 			else if(finalY > 768) {

				finalY = 1024;
			}
		}

		[UIView beginAnimations:nil context:NULL];
		[UIView setAnimationDuration:.35];
		[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
		[[sender view] setCenter:CGPointMake(finalX, finalY)];
		[UIView commitAnimations];
	}
}

Implementing Momentum

The final half of the above method is for us to calculate the momentum that the object will have after being moved. This will make the object appear as if it is being thrown across a table and slowly coming to stop. In order to do this we utilize UIPanGestureRecognizers velocityInView method which will tell us the velocity of the pan touch within a provided view. With this we can do an easy position calculation for both the x and y coordinates of our object. To do this we must provide an input for time, in this case .4 seconds. While this is not truly momentum and friction based physics it provides a nice affect for our interaction. With out final resting place calculated we ensure that where the object ends up will still be within the visible surface of the iPad by checking against the bounds of the screen depending on the current orientation. The final step is to animate the view moving to this final location over the same .4 second time period.

Implementing Taps

We have one final gesture recognizer implementation to do and that is the tap: method. This method will be used when a user taps an object that is in the midsts of sliding after being moved. We essentially want to stop the movement mid slide. In order to do that we are going to tell the CALayer layer property of our view to cancel all current animations. The short piece of code can be seen below.

-(void)tapped:(id)sender {

	[[[(UITapGestureRecognizer*)sender view] layer] removeAllAnimations];
}

Implementing UIGestureDelegate

If you run the code now you will be able to perform all of the gestures described within this document but you will notice that you are not able to do several at the same time. For instance you can not pinch zoom and rotate a view at the same time. This is because we still need to implement the UIGestureRecognizerDelegate method gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer. We want to make sure that any gesture recognizers can happen together except for the pan gesture recognizer. To do this we simply check that it is not a UIPanGestureRecognizerClass and return true in that case. See the short code below.

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {

	return ![gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]];
}

GIT Hub

You can find this project now on GitHub. Please let me know any issues you may have. Happy coding!

Follow me on Twitter @cruffenach

Cloning UIImagePickerController using the Assets Library Framework

PostPic

Hello iCoders. This is a follow up post to my initial post on the Assets Library Framework and Blocks. We came across an interesting problem when working on the application for Animoto.com. They have had an app in the store since the very early days of the app store, and one of our biggest struggles has been creating an interface to allow users to select multiple photos from their photo album for a slideshow. While the iPhone photos application allows for this, the UIImagePicker does not. With the release of the Assets Library framework we can now recreate the experience of the UIImagePicker, with some additional custom functionality to fit our needs. As a result we created a new cloned version of the UIImagePicker that allows for multiple photo selection just like the photos app. We have decided to open source the controller for other developers to use. This post will explain the process of creating the new image picker as well as the method of incorporating it into your code.

The ELCImagePickerController

The ELCImagePickerController is only possible because of the newly introduced Assets Library framework. This framework gives you raw (more or less) access to the photo and video elements of a user. We looked at UIImagePickerController and saw a lot of weaknesses with it. You can only select 1 photo at a time, and even in Apple’s photo app, where you can choose several pictures at a time, you can’t use multi touch to do your selection. To solve these problems we rolled our own solution that works very closely to UIImagePickerController.

How to use it

First I am going to explain using the picker since to many people the process of creating it won’t be very interesting. The image picker is created and displayed in a very similar manner to the UIImagePickerController. The sample application that is part of the GitHub project, where I distribute the controller, shows its use, but I will go into detail here. To display the controller you instantiate it and display it modally like so.

ELCImagePickerController *controller = [[ELCImagePickerController alloc] initImagePicker];
[controller setDelegate:self];
[self presentModalViewController:controller animated:YES];
[controller release];

The ELCImagePickerController will return the select images back to the ELCImagePickerControllerDelegate. The delegate contains to methods very similar to the UIImagePickerControllerDelegate. Instead of returning one dictionary representing a single image the controller sends back an array of similarly structured dictionaries. The two delegate methods are:]

- (void)elcImagePickerController:(ELCImagePickerController *)picker didFinishPickingMediaWithInfo:(NSArray *)info;
- (void)elcImagePickerControllerDidCancel:(ELCImagePickerController *)picker;

GIT Hub

You can find this project now on GitHub. Please let me know any issues you may have and look for future releases with feature enhancements

General Structure

The ELCImagePicker is a collection of UITableViewControllers that are placed inside a UINavigationController. While the ELCImagePickerController is actually 5 separate custom classes I have written, I have put them all within a single header and main. I chose this to make the classes that were required to be imported into a project when using this as few as possible.  While usually when presenting a UINavigationController you would create one yourself in code and use the initWithRootViewController method, in this case we have created a UINavigationController subclass called ELCImagePickerController which does all this behind the scenes. In the end all a developer has to do is use the initImagePicker method and present the controller modally. This lets the class match the functionality of UIImagePickerController closer. You can see the Header and Main for the ELCImagePickerController class on the next page.

ELCImagePickerControllerDemo from Collin Ruffenach on Vimeo.

Adding Local Weather Conditions To Your App (Part 2/2: Accessing Google’s XML Weather API)

Screen shot 2010-09-29 at 12.41.17 PM


In this Part 2 of ‘Adding Local Weather Conditions To Your App’, I’ll show you how to quickly add current temp, conditions, and today’s high / low temperature to your app.

If you’re lucky enough to already have the user’s zipcode or city and state, this should go very quickly for you. Otherwise, check out Part 1 (Integrating CoreLocation).

Let’s get started.

There are a handful of solid XML Weather APIs out there. The best one I’ve seen so far is Wunderground’s (it’s extremely well documented) but for the purposes of this tutorial, I decided to use Google’s “super secret” Weather API. It’s incredibly simple and should take care of all your basic weather needs. Though if you’re planning on releasing a production App, be sure to pick a public API and check out their TOS (some require API keys, or fees for production use). Here’s a good list of Weather APIs

Let’s look at some example calls to Google:

http://www.google.com/ig/api?weather=01451
http://www.google.com/ig/api?weather=nyc
http://www.google.com/ig/api?weather=Portland,OR
http://www.google.com/ig/api?weather=Jamaica

As you can see, there’s a bit of flexibility in how you can query the service. The one piece it’s lacking is querying by latitude and longitude. Lucky for you, I’ll show you how to use MKReverseGeocoder to determine your user’s City/State, which you can then plug right into the GET request. First, let’s take a quick look at the XML that comes back from the API:

  
    
      
        
        
        
        
        
        
        
      
      
        
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
     
    
      
      
      
      
      
    
  

All this should be pretty self explanatory. The two pieces to pay attention to here are current_conditions, and forecast_conditions. For our demo app, we’re simply going to display current temperature, conditions, a conditionsIcon, and today’s high and low temp. We’ll be able to pull all this information out of current_conditions and the first forecast_conditions (which is the forecast for today). In the interest of keeping everything organized, let’s build a class to hold our weather info.

//
//  ICB_WeatherConditions.h
//  LocalWeather
//
//  Created by Matt Tuzzolo on 9/28/10.
//  Copyright 2010 iCodeBlog. All rights reserved.
//

@interface ICB_WeatherConditions : NSObject {
    NSString *condition, *location;
    NSURL *conditionImageURL;
    NSInteger currentTemp,lowTemp,highTemp;
}

@property (nonatomic,retain) NSString *condition, *location;
@property (nonatomic,retain) NSURL *conditionImageURL;
@property (nonatomic) NSInteger currentTemp, lowTemp, highTemp;

- (ICB_WeatherConditions *)initWithQuery:(NSString *)query;

@end

In the .m we’re going to pull the data out of the XML and store it in our properties. There are several 3rd party Objective-C XML parsers. I’ve chosen to use Jonathan Wight’s TouchXML as it’s become somewhat of a standard for parsing XML on iOS. You can find it here. You’ll have to jump through a couple hoops to get TouchXML into your project. Here’s an excellent tutorial on installing TouchXML that will walk you through the whole process if you’ve never done it before.

//
//  ICB_WeatherConditions.m
//  LocalWeather
//
//  Created by Matt Tuzzolo on 9/28/10.
//  Copyright 2010 iCodeBlog. All rights reserved.
//

#import "ICB_WeatherConditions.h"
#import "TouchXML.h"

@implementation ICB_WeatherConditions

@synthesize currentTemp, condition, conditionImageURL, location, lowTemp, highTemp;

- (ICB_WeatherConditions *)initWithQuery:(NSString *)query
{
    if (self = [super init])
    {
        CXMLDocument *parser = [[[CXMLDocument alloc] initWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://www.google.com/ig/api?weather=%@", query]] options:0 error:nil] autorelease];

        condition         = [[[[[parser nodesForXPath:@"/xml_api_reply/weather/current_conditions/condition" error:nil] objectAtIndex:0] attributeForName:@"data"] stringValue] retain];
        location          = [[[[[parser nodesForXPath:@"/xml_api_reply/weather/forecast_information/city" error:nil] objectAtIndex:0] attributeForName:@"data"] stringValue] retain];

        currentTemp       = [[[[[parser nodesForXPath:@"/xml_api_reply/weather/current_conditions/temp_f" error:nil] objectAtIndex:0] attributeForName:@"data"] stringValue] integerValue];
        lowTemp           = [[[[[parser nodesForXPath:@"/xml_api_reply/weather/forecast_conditions/low" error:nil] objectAtIndex:0] attributeForName:@"data"] stringValue] integerValue];
        highTemp          = [[[[[parser nodesForXPath:@"/xml_api_reply/weather/forecast_conditions/high" error:nil] objectAtIndex:0] attributeForName:@"data"] stringValue] integerValue];

        conditionImageURL = [[NSURL URLWithString:[NSString stringWithFormat:@"http://www.google.com%@", [[[[parser nodesForXPath:@"/xml_api_reply/weather/current_conditions/icon" error:nil] objectAtIndex:0] attributeForName:@"data"] stringValue]]] retain];
    }

    return self;
}

- (void)dealloc {
    [conditionImageURL release];
    [condition release];
    [location release];
    [super dealloc];
}

@end

I’ve decided to write my own init method to handle making the request to our API. This will make for a clean implementation in our view controller.

Before we get to implementing ICB_WeatherConditions, I’ll touch briefly on location. Part 1/2 of this tutorial covered finding your user’s latitude and longitude with Core Location. Use MKReverseGeocoder to find city/state from coordinates. Start by adding both the MapKit and CoreLocation frameworks to your project.

MKReverseGeocoder works asynchronously to resolve location info; it has a delegate. In our example we set the delegate to the view controller (self). Be sure to add to your header as well. Since your view controller is now the delegate for the geocoder, make sure to implement the delegate methods for the MKReverseGeocoderDelegate protocol:

#pragma mark MKReverseGeocoder Delegate Methods
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)placemark
{
    [geocoder release];
    [self performSelectorInBackground:@selector(showWeatherFor:) withObject:[placemark.addressDictionary objectForKey:@"ZIP"]];
}

- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error
{
    NSLog(@"reverseGeocoder:%@ didFailWithError:%@", geocoder, error);
    [geocoder release];
}

Now we’re ready to implement ICB_WeatherConditions. I usually populate UILabels in viewDidLoad, but since we’re making API calls and downloading a remote image (the weather conditions icon), I decided to write a method to execute in the background. This lets us use synchronous requests (which a lot easier to deal with) to handle network requests without locking up the main thread. Once our network calls have finished, we call back to the main thread to update the UI accordingly.

// This will run in the background
- (void)showWeatherFor:(NSString *)query
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    ICB_WeatherConditions *weather = [[ICB_WeatherConditions alloc] initWithQuery:query];

    self.conditionsImage = [[UIImage imageWithData:[NSData dataWithContentsOfURL:weather.conditionImageURL]] retain];

    [self performSelectorOnMainThread:@selector(updateUI:) withObject:weather waitUntilDone:NO];

    [pool release];
}

// This happens in the main thread
- (void)updateUI:(ICB_WeatherConditions *)weather
{
    self.conditionsImageView.image = self.conditionsImage;
    [self.conditionsImage release];

    [self.currentTempLabel setText:[NSString stringWithFormat:@"%d", weather.currentTemp]];
    [self.highTempLabel setText:[NSString stringWithFormat:@"%d", weather.highTemp]];
    [self.lowTempLabel setText:[NSString stringWithFormat:@"%d", weather.lowTemp]];
    [self.conditionsLabel setText:weather.condition];
    [self.cityLabel setText:weather.location];

    [weather release];
}

Of course make sure your NIB is connected to your IBOutlets properly.

Now the final piece:

[self performSelectorInBackground:@selector(showWeatherFor:) withObject:@"97217"];

Build and Run:

And you’re done!

You can see the complete class below. I’ve also posted a demo project to github.

My name is Matt Tuzzolo (@matt_tuzzolo). I hope you found this post helpful.

//
//  LocalWeatherViewController.h
//  LocalWeather
//
//  Created by Matt Tuzzolo on 8/30/10.
//  Copyright iCodeBlog LLC 2010. All rights reserved.
//

#import 
#import "MapKit/MapKit.h"

@interface LocalWeatherViewController : UIViewController  {
    IBOutlet UILabel *currentTempLabel, *highTempLabel, *lowTempLabel, *conditionsLabel, *cityLabel;
    IBOutlet UIImageView *conditionsImageView;
    UIImage *conditionsImage;
}

@property (nonatomic,retain) IBOutlet UILabel *currentTempLabel, *highTempLabel, *lowTempLabel, *conditionsLabel, *cityLabel;
@property (nonatomic,retain) IBOutlet UIImageView *conditionsImageView;
@property (nonatomic,retain) UIImage *conditionsImage;

- (void)updateUI:(ICB_WeatherConditions *)weather;

@end

And the .m:

//
//  LocalWeatherViewController.m
//  LocalWeather
//
//  Created by Matt Tuzzolo on 8/30/10.
//  Copyright iCodeBlog LLC 2010. All rights reserved.
//

#import "LocalWeatherViewController.h"
#import "ICB_WeatherConditions.h"
#import "MapKit/MapKit.h"

@implementation LocalWeatherViewController

@synthesize currentTempLabel, highTempLabel, lowTempLabel, conditionsLabel, cityLabel;
@synthesize conditionsImageView;
@synthesize conditionsImage;

- (void)viewDidLoad {
    [super viewDidLoad];

    if (1) //you have coordinates but need a city
    {
        // Check out Part 1 of the tutorial to see how to find your Location with CoreLocation
        CLLocationCoordinate2D coord;
        coord.latitude = 45.574779;
        coord.longitude = -122.685366;

        // Geocode coordinate (normally we'd use location.coordinate here instead of coord).
        // This will get us something we can query Google's Weather API with
        MKReverseGeocoder *geocoder = [[MKReverseGeocoder alloc] initWithCoordinate:coord];
        geocoder.delegate = self;
        [geocoder start];
    }
    else // You already know your users zipcode, city, or otherwise.
    {
        // Do this in the background so we don't lock up the UI.
        [self performSelectorInBackground:@selector(showWeatherFor:) withObject:@"97217"];
    }
}

- (void)showWeatherFor:(NSString *)query
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    ICB_WeatherConditions *weather = [[ICB_WeatherConditions alloc] initWithQuery:query];

    [self.currentTempLabel setText:[NSString stringWithFormat:@"%d", weather.currentTemp]];
    [self.highTempLabel setText:[NSString stringWithFormat:@"%d", weather.highTemp]];
    [self.lowTempLabel setText:[NSString stringWithFormat:@"%d", weather.lowTemp]];
    [self.conditionsLabel setText:weather.condition];
    [self.cityLabel setText:weather.location];

    self.conditionsImageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:weather.conditionImageURL]];

    [weather release];

    [pool release];
}

#pragma mark MKReverseGeocoder Delegate Methods
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)placemark
{
    [geocoder release];
    [self performSelectorInBackground:@selector(showWeatherFor:) withObject:[placemark.addressDictionary objectForKey:@"ZIP"]];
}

- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error
{
    NSLog(@"reverseGeocoder:%@ didFailWithError:%@", geocoder, error);
    [geocoder release];
}

- (void)didReceiveMemoryWarning {
     [super didReceiveMemoryWarning];
}

- (void)viewDidUnload {
	// Release any retained subviews of the main view.
	// e.g. self.myOutlet = nil;
}

- (void)dealloc {
    [super dealloc];
}

@end

M16 Golf Ball Launcher

Have a really bad golf game? Can’t quite get your drives down where you want them? Well, if you have a spare M16 laying around you can launch your golf balls much farther than you ever could with a driver.

The $20 add-on to the barrel of an M16, M4 or AR-15 allows you to chuck golf balls at incredible speeds. By loading the magazine with blanks, gasses from the blanks will shoot the golf ball out of the end like a cannon. The ball is good for as much as 400 yards and you can shoot the thing about 200 yards with 5-10 feet of drop.

I like to golf myself but am more of a traditionalist; Alcohol on the golf course and semi automatic weapons just don’t seem like a good idea to me.

[Link to ARR-225]

tech.nocr.atM16 Golf Ball Launcher originally appeared on tech.nocr.at on 2011/02/14.

© tech.nocr.at 2011 |
Permalink |
Comments |
Read more in Gadgets |
Add to del.icio.us |
Stumble it |
Digg this

Explore more in: , , , ,


Powertrekk Fuel Cell Charger

powertrekk_fuel_cell_charger.jpg

Sure, you could build your own solar powered battery charger, but that’s only good when the sun is shining. If your conscious about our environment the Powertrekk Fuel Cell Charger allows your to charge up your USB gadgets with an incredible small impact to the environment. Plus, no need for the sun, just some H2O.

Drop a tablespoon of water into the fuel cell and your good to go. The Powertrekk will charge up anything that can be charged via USB. It’s not the smallest charger out there, but it is one of the greenest we have seen.

Currently there is no price or retail locations announced, but check out their site for updates.

[Link to Powertrekk]

tech.nocr.atPowertrekk Fuel Cell Charger originally appeared on tech.nocr.at on 2011/02/13.

© tech.nocr.at 2011 |
Permalink |
One Comment |
Read more in Gadgets |
Add to del.icio.us |
Stumble it |
Digg this

Explore more in: ,


Angry Birds Bento Box

angry_birds_bento_1.jpg

I love Angry Birds just as much as the next guy, but Angry Birds sushi, I think not. But if you consider yourself the uber-fan and already have all of the plush toys and a real life Angry Birds set in your backyard then you must try the Angry Birds bento box. I hear it’s tasty

The edible creation was made by the guys at mymealbox. They guarantee us that no birds or pigs were harmed in the production of these bento boxes.

Now all you need is to create a slingshot for the birds and have someone launch them into your mouth. Just make sure they have some good aim.

tech.nocr.atAngry Birds Bento Box originally appeared on tech.nocr.at on 2011/02/13.

© tech.nocr.at 2011 |
Permalink |
Comments |
Read more in Gaming |
Add to del.icio.us |
Stumble it |
Digg this

Explore more in: , , ,


DIY: Solar Battery Charger

4-Solar-Battery-Charger.jpg

Here is a super easy and super cheap way to build a solar battery charger. You will need nothing more than 2 solar panels, a battery holder and a blocking diode.

The project is dirt cheap, usually running you no more than 3 or 4 bucks and is a great way to experiment with solar power. The unit will trickle charge your NiMH rechargeable batteries and can usually charge 2 AA’s fully with about one full day’s worth of sunlight. With some tweaking you could add a USB port to the design so you can keep your iPhone or other gadget that uses a USB connection fully charged all the time.

Check out Instructables for a parts list and a complete project breakdown.

tech.nocr.atDIY: Solar Battery Charger originally appeared on tech.nocr.at on 2011/02/13.

© tech.nocr.at 2011 |
Permalink |
Comments |
Read more in Hacking And Security |
Add to del.icio.us |
Stumble it |
Digg this

Explore more in: , , ,


Tiny DreamPlug PC

dreamPC-plug.jpg

Shrinking these is all the rage. Why not shrink your PC as well. This wall-wart looking device has a full PC crammed inside of it, not quite a full on gaming machine, but perfect for a small file server.

This all in one wonder will only run you $150 and comes with some surprising specs. A 1.2GHz Marvell Sheeva ARM processor, 512MB of DDR-2-800MHz RAM, 1GB of microSD storage for system files, two gigabit ethernet ports, two USB 2.0 ports, an eSATA 2.0 port, SD card slot, Wi-Fi b/g, Bluetooth and a headphone jack. The company also claims that it can save up to 96% on energy costs compared to a standard desktop PC.

This is perfect for small applications like a file server or for cloud computing expansion. Imagine having a power-bar with a few of these plugged in.

[Link to DreamPlug PC]

tech.nocr.atTiny DreamPlug PC originally appeared on tech.nocr.at on 2011/02/11.

© tech.nocr.at 2011 |
Permalink |
Comments |
Read more in Gadgets |
Add to del.icio.us |
Stumble it |
Digg this

Explore more in: , , ,


eLocity A10 Up For Pre-Order On The 15th

A10 Front-1000px.jpg

The big brother to the popular eLocity A7, the A10, will be available for pre-order later this month on the 15th.

The A10 will feature a NVIDIA Tegra 2 processor, 512 MB of Ram, 10.1-inch 1366×768 multi touch screen and will ship with Android 2.2 (Froyo). Following in the A7′s footsteps, the A10 will also include HDMI, a USB port, and a microSD slot to extend it’s storage by 32GB if you so choose to buy that big a card.

If it’s anything like the A7 it’s sure to be a hot ticket. As soon as I can get my hands on one i’ll pass on my likes and dislikes as usual.

Pre-orders start on the 15th of this month. They will be available in 3 models: The A10 4GB (A10.004), 32GB (A10.032) and 64GB (A10.064) with the 4GB units starting at the discounted price of $399 for early orders, the 32GB model retailing at $599.99 and the 64GB at $699.99.

Check out elocitynow.com for more info.

tech.nocr.ateLocity A10 Up For Pre-Order On The 15th originally appeared on tech.nocr.at on 2011/02/11.

© tech.nocr.at 2011 |
Permalink |
2 Comments |
Read more in Gadgets |
Add to del.icio.us |
Stumble it |
Digg this

Explore more in: , ,


Sony Gets Geohot’s Hard Drive

geohot-pc.jpg

Seems like the Sony V. Geohot saga continues. According to Wired, U.S. District Judge Susan Illston has order George Hotz to hand his computer over to Sony so that they can search his hard drive. This comes after the court’s previous decision to grant Sony a temporary restraining order against Hotz.

Geohot’s attorney objected to the order saying “by allowing Sony access to Mr. Hotz’s computer they would be able to see the contents of his client’s files”. Judge Illston replied with “That’s the breaks”.

“Here, I find probable cause that your client has got these things on his computer,” Illston said. “It’s a problem when more than one thing is kept on the computer. I’ll make sure the order is and will be that Sony is only entitled to isolate … the information on the computer that relates to the hacking of the PlayStation.”

Sony sure is throwing it’s massive might behind their case against Hotz. One must wonder if they just want to make an example out of him in an attempt to deter other would be hackers from hacking their products. I’m not sure what they expect to achieve; we all know that once something is on the internet it lives forever.

tech.nocr.atSony Gets Geohot’s Hard Drive originally appeared on tech.nocr.at on 2011/02/11.

© tech.nocr.at 2011 |
Permalink |
2 Comments |
Read more in Tech News |
Add to del.icio.us |
Stumble it |
Digg this

Explore more in: , , ,


DIY: Boost Your Laptop’s WiFi

wifisignalboost.jpg

Even with the hardware of today you can still experience horrible wireless reception. Dead spots have become all too common and if your wifi access point happens to be on another floor, you know how much of a pain it can be to stream media when your signal sucks.

Sure you could go out and buy some massive transmitter that could cook bacon, but not only is it dangerous it’s also really expensive. By using some velcro, an MC card connector and a Dremel, you can create this Frankenstein of signal boosters for your laptop. You can easily steal borrow the wifi signal from your neighbours place.

If your not crazy about building something like this and want something easier then check out the wifi wok.

[Link to Hook Up]

tech.nocr.atDIY: Boost Your Laptop’s WiFi originally appeared on tech.nocr.at on 2011/02/10.

© tech.nocr.at 2011 |
Permalink |
Comments |
Read more in Hacking And Security |
Add to del.icio.us |
Stumble it |
Digg this

Explore more in: , , ,


Etch-A-Sketch Computer

etchasketch.jpg

Who needs a touch screen when you can have you very own etch-a-sketch computer. Modder Martin Raynsford created this etch-a-sketch computer with some MDF and a few bottle caps. There is actually a little more involved than that, but it definitely is a cool mod.

By reusing the X and Y axis rollers, the LED and opto sensor of a trackball mouse the bottle caps can be used to move the mouse. The left and right click sensors were also places behind the bottle cap knobs.

It probably won’t get you many girls, but it will raise your geek-cred. Check out Martin’s site for a complete write up on the project.

tech.nocr.atEtch-A-Sketch Computer originally appeared on tech.nocr.at on 2011/02/09.

© tech.nocr.at 2011 |
Permalink |
Comments |
Read more in Hacking And Security |
Add to del.icio.us |
Stumble it |
Digg this

Explore more in: , , ,


Put Windows Phone 7 On Your iPhone, Sort Of

454f136fa088584a1ad1c674cfe078a0368750bd21ed621cb6f6593aab916ad96g.jpg

Are you just itching to put Windows Phone 7 on your iPhone? Me either, but if for some crazy idea you want to make your iPhone look like it’s running Windows Phone 7 you now can.

Take one jailbroken iPhone and Wyndwarrior’s “OS7” and you can give the appearance that your iPhone is running Windows Phone 7. It’s currently under beta and I highly doubt Apple will ever let it step foot in the App Store. If you wish to be mocked by your friends do it yourself, a full write up is available on modmyi.com or here are the basics of getting it going:

1. Install Backboard from Cydia.

2. Make sure you have created a “Default” backup; if not, press the add button in Backboard and give it a name.

3. Go on your device and click on this link: backboard://http://wyndrepo.googlecode.com/files/OS7Beta.zip

4. Press “Yes” and wait for it to download. This may take a few minutes depending on your internet connection.

5. Select OS7 from Backboard. Press Install. Respring.

tech.nocr.atPut Windows Phone 7 On Your iPhone, Sort Of originally appeared on tech.nocr.at on 2011/02/08.

© tech.nocr.at 2011 |
Permalink |
Comments |
Read more in Mac |
Add to del.icio.us |
Stumble it |
Digg this

Explore more in: ,


DIY: Macro Lens

diy_macro.jpg

Macro photography can be beautiful. Close up shots of tiny objects, their detail shines through so crisp and vibrant. The problem is that you aren’t going to pull off those incredible shots with your iPhone, you won’t even be able to pull them off with your standard DSLR lens. Special macro lenses can be quite expensive, usually well out of the reach of amateur photo-bugs.

Instructable user Sneipiller has a great write up on how you can build your own macro lens on the super cheap. You won’t actually be building a lens, but a tube extension for you current lens. By extending the focal point of a standard lens you can achieve some great shots like this one:

F0EE8JBGJC3T9C6.LARGE_rect540.jpg

The project is super easy and all you really need is a tube (like a gift wrapping paper roll), a Dremel, black electrical tape and a hot glue gun. A full write-up on the project is over on Instructables.

tech.nocr.atDIY: Macro Lens originally appeared on tech.nocr.at on 2011/02/07.

© tech.nocr.at 2011 |
Permalink |
Comments |
Read more in Hacking And Security |
Add to del.icio.us |
Stumble it |
Digg this

Explore more in: , ,