Showing posts with label iphone. Show all posts
Showing posts with label iphone. Show all posts

Sunday, January 24, 2010

Cocoa and Delegates

Delegates are probably one of the toughest concepts for iPhone/OSX developer newcomers to grasp. I'm going to try to keep it simple, and demonstrate how to build your own delegate protocol into your classes.

A delegate is merely a design pattern in Objective-C. Simply put, it provides a way for objects to "listen" for interesting things happening in your object and act on them without your object knowing anything about the object(s) listening. Delegates are used heavily in the Cocoa environment. Most often you will see delegates used to trigger methods on a UIViewController, although they can be implemented on anything.

Let's think for a moment what type of problem this solves. Let's say we have a UIViewController which has a UITableView in its view. When a table row is tapped, we want to slide a new view onto the screen. We don't want UITableView handling this task, so how do we accomplish this?

The UIViewController needs to handle the new view (thus it's name.) So the UITableView needs to somehow inform the UIViewController that a row was tapped so it can take action. Therefore, the UIViewController needs to be the delegate of the UITableView. So we need to setup two things: We need to inform the UITableView what it's delegate object is, and we need to inform the delegate object what delegate protocol(s) it conforms to.

We begin by setting the delegate property of the UITableView to the UIViewController object. If you are using Interface Builder, you can simply connect the delegate property of the UITableView to the UIViewController (typically the File's Owner.) Otherwise you might handle this in the viewDidLoad{} of the UIViewController, something like:

self.tableView.delegate = self;


You will also want to tell the view controller that it understands the UITableViewDelegate protocol, so you would put this in the UIViewController .h interface declaration:

@interface MyViewController : UIViewController
<UITableViewDelegate> {}
(Note: if your view controller is an extension of UITableViewController, you will not need to declare the UITableViewDelegate protocol.)

And you are set! Now when you implement a method of the UITableViewDelegate in the UIViewController, this method will trigger when something happens in the table view for that delegate method. So for instance, you put this in your UIViewController .m file:


- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// push the new view onto the stack here
}
When a row is selected in the table, this method will get triggered.

So now begs the question, how do you add your own delegate protocol to your custom classes? Here is an example. Let's say you want to be able to trigger methods of other classes when interesting things happen in your class. The first thing you need to do is define what delegate methods are available to the delegate object. You would first put something like this at the top of your class .h file, above the implementation declaration:


@protocol MyCustomClassDelegate <NSObject>;
- (void)didClickDone:(UIButton *)button;
@optional
-(void)didClickCancel:(UIButton *)button;
@end


Where MyCustomClassDelegate is the name of your class delegate protocol (your class name appended with "Delegate"), and didClickDone and didClickCancel are the names of your two delegate methods. You can add as many methods as you wish. You not need to implement these methods in your class, that is up to the delegate to do. Notice the line with @optional. This means that the following methods are optional for the delegate to implement.

Now we need to setup the delegate property:


@interface MyCustomClass : NSObject {
id <MyCustomClassDelegate> delegate;
}

@property (nonatomic, assign)
id <MyCustomClassDelegate> delegate;

And finally in the .m file, synthesize the delegate:


@synthesize delegate;


Note we do not need to release the delegate in the dealloc method, as this is not a retained object, it is just an assigned id.

Ok, for the final part of your class, you need to fire off the delegate method when something interesting happens. For instance, someone clicks the done button. In your IBAction for the done button, you do just that:


-(IBAction)done:(UIButton *)button {
[self.delegate didClickDone:button];
}


And so on, for each delegate method. Ok, that is it for the class. Now, for the delegate class. To be the delegate of MyCustomClass, you need to declare that you implement the MyCustomClassDelegate protocol (.h file):


@interface MyViewController:
UIViewController <MyCustomClassDelegate> { }


And then, we implement the delegate methods (.m file):


-(void)didClickDone:(UIButton *)button {
// do something here!
}


One last thing to note, a class can be the delegate for several objects, just comma-separate the delegates:


@interface MyViewController:
UIViewController <MyCustomClassDelegate,SomeOtherDelegate> { }



I hope that helps clear up delegates a bit! Please leave your comments below.

Thursday, May 14, 2009

Camera image orientation

I was having a confusing problem where images taken from the iPhone camera were being displayed in the wrong orientation. I would take a photo in portrait and save the data to disk. Later when I retrieved the photo it would show up rotated 90 degrees.

The iPhone knows what orientation a photo is taken, and this orientation depends on how the camera is being held when you snap the photo. You can get the orientation value on a UIImage from the property imageOrientation.

When you save the photo, it is possible that this information could get lost, depending on the way you save it. If it does get lost, the UIImageView will assume the photo was taken as a left-side landscape photo, and display it with that orientation.

I ran into a problem when trying to save the camera image with UIImagePNGRepresentation(). For some reason this loses the orientation info. I switched to UIImageJPEGRepresentation() and all is well now.

So basically if you run into the problem, you have 2 choices: Be sure you use a format that preserves orientation data, or rotate the UIImage data directly before saving it to a file.

Wednesday, May 13, 2009

Rotating view around arbitrary point the easy way

I have a UIImageView that I want to rotate around a given point. By default, the layer will rotate around the center of the UIImageView. Trying to change this anchor point involves a bit of work, either move at the beginning and reposition all the view elements, or translating the view, rotating, and translating back. The last option works fine, unless you are trying to use core animation to smoothly rotate the view.

I found out a much easier solution to the whole problem. You could call it a workaround, but it works well without disturbing the view you want rotated.

Basically, you create a new UIView. Place the center point where you want the rotation to occur. Change the size of the view so that the view you want rotated will fit inside. Make your view (you want rotated) a subview of the new view. Now, just rotate the new view, and your subview will be rotated correctly.

Example: lets say we have a UIImageView that is 320x480 (the size of the whole iPhone screen). We want it to rotate from the bottom center instead of its middle. Create a new UIView that is 0,0,320,960. This is basically double the height of the screen, positioned so the bottom half is off the screen, which places the center point right where we want it. Now place your UIImageView inside the new UIView in the top half (visible on the screen.) Now when you rotate the UIView, it will pivot on the bottom center of the screen, and your UIImageView will rotate along with it, as expected.

Tuesday, March 24, 2009

Reset a UIScrollView

This has to be the most puzzling thing I've come across in iphone app building thus far. I wanted to "reset" a UIScrollView after pinch zooming and scrolling. It turns out, there is no way to reset the zoom factor on the contentView in a UIScrollView. At least not in the current SDK. You must completely replace the subview of the scroll view to work around it.

I wanted this transition to happen smoothly, so I used some core animation. Here is some working code to get the scroll view to "reset". In my app, I implemented this after a double tap, and after an orientation change.

self.scrollView is the UIScrollView, and self.imageView is the UIImageView subview.


- (void) resetImageZoom {

// animate the transition
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(resetAnimFinish:finished:context:)];
[UIView setAnimationDuration:0.4];

// reset the scrollview and the current image view
self.scrollView.transform = CGAffineTransformIdentity;
self.scrollView.contentOffset = CGPointZero;
self.imageView.frame = self.scrollView.frame;
self.imageView.center = self.scrollView.center;
self.scrollView.contentSize = self.imageView.frame.size;

[UIView commitAnimations];

}

-(void)resetAnimFinish:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {

// make a copy of the image view
UIImageView *copy = [[UIImageView alloc] initWithImage:self.imageView.image];
copy.autoresizingMask = self.imageView.autoresizingMask;
copy.contentMode = self.imageView.contentMode;
copy.frame = self.imageView.frame;
copy.center = self.imageView.center;

// replace the current image view with our copy
[self.imageView removeFromSuperview];
self.imageView = copy;
[self.scrollView addSubview:copy];
[copy release];
}

If you wanted, you could extend UIImageView an implement the NSCopying protocol, then just use the copy convenience method. I chose to copy the object a bit more manually here.

Sunday, March 22, 2009

stripping HTML with objective-c/cocoa

I was looking for a simple way to strip HTML from an NSString. Since NSString has no native regular expression support, I had to resort to other means. I found many posts requiring regex support libs and/or libxml2 support. Bleh. Then I found this simple solution using NSScanner. It worked well for me.

It's not super smart though. I'm guessing it will bork on any stray < or > tags in the text that are not part of HTML markup. Make sure they are escaped.

http://www.rudis.net/content/2009/01/21/flatten-html-content-ie-strip-tags-cocoaobjective-c

I made my own small addition, optionally trimming whitespace too.


- (NSString *)flattenHTML:(NSString *)html trimWhiteSpace:(BOOL)trim {

NSScanner *theScanner;
NSString *text = nil;

theScanner = [NSScanner scannerWithString:html];

while ([theScanner isAtEnd] == NO) {

// find start of tag
[theScanner scanUpToString:@"<" intoString:NULL] ;
// find end of tag
[theScanner scanUpToString:@">" intoString:&text] ;

// replace the found tag with a space
//(you can filter multi-spaces out later if you wish)
html = [html stringByReplacingOccurrencesOfString:
[ NSString stringWithFormat:@"%@>", text]
withString:@" "];

} // while //

// trim off whitespace
return trim ? [html stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] : html;

}

Tuesday, October 28, 2008

Getting URL from UIWebView

It thought I'd post how to get the current URL string from a UIWebView object. Here you go:


NSString *currentURL
= myWebView.request.URL.absoluteString;


That is assuming myWebView is your UIWebView object.

To open a web page in Safari from a UIWebView, do this:


[[UIApplication sharedApplication]
openURL:myWebView.request.URL];


Saturday, September 27, 2008

Enter text by swiping?

This is a very interesting idea, maybe apple could implement it?

http://www.swypeinc.com/product.html

The idea is to enter words by swiping your finger over the letters on the keyboard. You type a word by running your finger over the letters in one motion, and the phone figures out the word. It seems this would be a very quick way to type, so long as the word recognition is accurate.