Posts Tagged ‘UIView’

Creating a view programmatically

April 29th, 2009

Now it’ time for something more advanced. In Interface Builder how-to I told you, that I.B. is great for designing interfaces, but there are cases this knowledge – creating everything programmatically will be necessary and helpful, like:

  • there is an unknown number of object which will be added
  • the number of object is static but enormous, and they are almost the same with tiny differences
  • you want to be better than others and you like hard not-needed extra work…

Let’s start. So far you created everything in Interface Builder, you were editing NIB (.xib) files, but in fact Interface Builder created the whole code for you in the background.

As I mention in article about I.B. I told you, that you can create the Interface only in I.B., partialy in I.B. and Xcode or only in Xcode.

I personally advice you to use Interface Builder wherever possible using Xcode when necessary, if some properties cannot be set in I.B.. In this article I will show you how to create everything using only Xcode. Let’s start.

Whenever you created any object to be visible in Interface Builder you used the keyword IBOutlet for example: IBOutlet UILabel *myLabel;. If you customize in this case myLabel in Xcode only, you can skip this keyword, so: UILabel *myLabel;. The same with any methods / actions, you were using IBAction what in fact is an equivalent to void (no value returned), but IBAction gives you one advantage – that it’s visible in I.B.. So if are impementing a method that will be called by objects created only in Xcode you can use: -(void)myAction; instead of -(IBAction)myAction; and of course if you need to know what object called the method: -(void)myAction:(id)sender instead of -(IBAction)myAction:(id)sender.

Creating a view programmaticaly

Let’s start by creating programmaticaly a view. Let’s add a UIViewController class, name it MyView.m (and MyView.h). In you AppDelegate’s header add standard lines:

#import "MyView.h"
@class MyView;

and in AppDelegate’s implementation:

MyView *viewController = [MyView alloc];
[window addSubview:viewController.view];

Now go to MyView.m, and find somewhere the commented loadView method, it should look like:

/*
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
}
*/

Deleate /* and */ to activate this method. During allocating of this class (or any UIViewController) the loadView will be automatically called to load the view (and BTW, once view is loaded viewDidLoad is called). Extend loadView class by these two lines:

	self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
	self.view.backgroundColor = [UIColor yellowColor];

If you Build and Go you will get the empty yellowyellowview screen (with status bar of course).

Why?

It’s simple. first line is creating a new view [UIView alloc]. Because MyView class is UIViewController it already expects a view. So far, you created a view in Interface Builder connecting view outlet to view’s preview, if I can call it like this, this time you created it programmatically. The second part is creating the view’s frame, you are setting the coordinates and size of view. Quick info about CGRectMakes’s arguments:

CGRectMakes(xTopLeftCorner,yTopLeftCorner,width,height)

The 2nd line is quite obvious, just changing the background color. Hmm, but where did you put anything to make this view visible? Answer: in AppDelegate implementation file. So, to be more specific, visit your …AppDelegate.m file:

  • MyView *viewController = [MyView alloc] is creating new object of class MyView and automatically calls loadView method, so you don’t have to
  • [window addSubview:viewController.view] is adding the view created by loadView to the application window

Hope it’s clear now.

Adding a label programmatically

Add this block of code to loadView or viewDidLoad method:

	UILabel *myLabel2 = [[UILabel alloc] initWithFrame:CGRectMake(50, 200, 200, 80)];
	myLabel2.text = @"only Xcode";
	myLabel2.textAlignment = UITextAlignmentCenter;
	myLabel2.textColor = [UIColor yellowColor];
	myLabel2.shadowColor = [UIColor whiteColor];
	myLabel2.shadowOffset = CGSizeMake(1,1);
	myLabel2.font = [UIFont fontWithName:@"Zapfino" size:20];
	myLabel2.backgroundColor = [UIColor greenColor];
	[self.view addSubview:myLabel2];
	[myLabel2 release];

Code is similar to that one, where you created the view. Differences? [UILabel alloc], not [UIView alloc] and another frame which suits me. Next 7 lines is a customization of label – changing label’s text, text alignment, text color, shadow, font, also background color. To show label on view you use [self.view addSubview:myLabel2];. I’ve already told you once, but I will repeat, almost everything visible on iPhone / iPod touch is some kind of view. UILabel is also UIView’s subclass.

The last line contains: [myLabel2 release]; it frees our memory, but use it wisely. This time I didn’t put it into dealloc method that is called when application terminates. Hmmm… no…, read the article about Memory management and you will see the point.

Now your app should look like this:

yellowviewlabel

You can easily add next label, remember to give it an unique name:

	UILabel *myLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 50, 200, 80)];
	myLabel.text = @"Isn't it amazing?";
	myLabel.textAlignment = UITextAlignmentCenter;
	myLabel.backgroundColor = [UIColor colorWithRed:0.2 green:0.9 blue:0.5 alpha:0.3];
	[self.view addSubview:myLabel]; 

Creating a button programmaticaly

Let’s start with the code:

	myButton = [[UIButton buttonWithType:UIButtonTypeRoundedRect] initWithFrame:CGRectMake(50, 300, 200, 80) ];
	[myButton setTitle:@"And a button" forState:UIControlStateNormal];
	[myButton setTitle:@"Alert :D " forState:UIControlEventTouchDown];
	[myButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
	myButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
	myButton.backgroundColor = [UIColor clearColor];
	[myButton addTarget:self action:@selector(buttonAction) forControlEvents:UIControlEventTouchUpInside];
	[self.view addSubview:myButton]; 

As you see above, this time I didn’t use something like [UIButton alloc], but [UIButton buttonWithType:...]. Why? Because I had to, if the button was simply an image I would use alloc, but as long as I want to use the predefined type I had to create it using above command.

Now you might ask a question why I set some properties using [myObject something:], and others using myObject.something=. Answer: some properties can be set using both of these two ways, some only using one. For example  in UILabel:

myLabel.text = @"asdfg";
[myLabel setText:@"asdfg"];

are the equivalents. So comming back to my button, I set two different titles, one is displayed during normal state of button, and the other while it’s being pressed, next color, alignment, backgroundColor. Later I connected an action to the button – in bold is a name of method called when user will TouchUpInside – press the button. All you need to do is to implement the buttonAction method:

-(void)buttonAction {
	UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Hmmm" message:@"It really works :) " delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:nil];
	[alert show];
	[alert release];
}

If you would like to have info about what object called the method [(id)sender], add a colon on the end of class name in adding a selector to the button:

	[myButton addTarget:self action:@selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside];

And of course:

-(void)buttonAction:(id)sender {
	//...
}

One thing you have to know

When you set a frame using CGRectMakes(TopLeftX,TopLeftY,width,height), or the center using CGPointMake(CenterX,CenterY), you need to know something more. These two x, y coordinates of top left corner or center point, are set according to the view (or it subclass) that our object is subview of . So let’s say you have a view 320×480px, but it is not placed in the center of the screen (window), it’s a little bit to the left. If you add a UILabel as the view’s subview and set the center property of that label equal CGPointMake(160,240), it will be in the exact center of the view, but together with the view moved a little bit to the left.

Conclusion

As you see, designing an interface strictly in Xcode is a little bit tiring, but there will be cases you will have to use it in practise. The difficult maybe the lack of knowledge what property customize some key and less importan parts of given object. Everything can be find in documentation. You can also get a list of all available properties pressing Esc-key after “myObject.” or “[myObject “.

As usuall I prepared a project for you, so if something doesn’t work for you, compare it with mine.

noibend

xcodeproj

Download the project

Making screenshots

April 28th, 2009

iPhone / iPod touch allows you to make screenshots (saves the contents as an image) by pressing both home and power button, although many users don’t know this functionality. If you go to your camera roll or sync photos with your computer, every screenshot you make is an image 320 x 480 px.

Today I will show you how to make a screenshot of arbitrary size using Quartz. What does arbitrary size mean? Well you can not only save the smaller part of your screen, but, if you for example have bigger view than 320×480 px you can save it all, not only the visible part.

Before you start, you have to add the QuartzCore framework to the project. All frameworks can be find here:

/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/ iPhoneOSxxx.sdk/System/Library/Frameworks

The part in bold can be different for each of you, in my case it’s iPhoneOS2.2.sdk. But there is much easier way to get to the Frameworks folder.

revealframeworkfinder

As you see above in Xcode, ctrl-press (or using your right mouse button) any of existing frameworks and choose Reveal in Finder.

finderframeworks

Now in Finder drag QuartzCore.framework to the Frameworks group into your project. It’s up to you if you decide to copy the item or simply link it. The framework has been added, now in the class which fill be making screenshots, you need to add a line to import the part of that framework:

#import <QuartzCore/QuartzCore.h>

You can add this line in either header (.h) or implementation file (.m).

Now it’s time to add the method which will make a screenshot and explain how it works:

-(void)saveToCameraRoll {
	UIGraphicsBeginImageContext(self.view.bounds.size);
	[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
	UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
	UIGraphicsEndImageContext();
	UIImageWriteToSavedPhotosAlbum(viewImage, self, nil, nil); 

	/* 
	flashView.alpha = 1;
	[UIView beginAnimations:nil context:NULL];
	[UIView setAnimationDuration:0.5];
	flashView.alpha = 0;
	[UIView commitAnimations];
	UIAlertView *alert= [[UIAlertView alloc] initWithTitle:nil message:@"Photo saved to your camera roll" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
	[alert show];
	[alert release];
	*/
}

I’ve implemented the saveToCameraRoll method were first five lines do everything you need. It’s quite difficult to discuss it, as long as I never mentioned about Quartz drawing, contexts, layers… But, first line creates the context of our view (UIViewController’s self.view) size. Using that context’s frame (it’s not frame properly speaking) it contents is rendered (2nd line) and saved to an viewImage. 4th line is closing the context as we do not need it anymore, and the 5th line puts viewImage to the Camera Roll.

How about the part in the comment ( /* and */ )? Well it’s my idea how to copy the animation iPhone uses while making the screenshot using home and power buttons. I created the flashView - simple white view of the same size of my self.view most time 100% transparent. When the user decides to create a screenshot using a button I provided in the application, flashView become visible and during half a second it’s transparent again. And the alert to inform what has just happen.

Please remember that above method saves the whole self.view and all it’s subviews. So whatever is a subview of self.view will be saved as well. You can change the alpha property of anything you don’t want to save and reveal what’s behind it, and after screenshot was made bring alpha to normal. While making a screenshot as long as it will be one procedure – one task user won’t see any changes in interface. Example:

-(void)saveToCameraRoll {	topView.alpha = 0;

	UIGraphicsBeginImageContext(self.view.bounds.size);
	[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
	UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
	UIGraphicsEndImageContext();
	UIImageWriteToSavedPhotosAlbum(viewImage, self, nil, nil); 

	topView.alpha = 1;

}

As always (don’t thank me) I provided an sample project. It alows you to make screenshot of whole iPhone screen (320 x 480 px), only central part (320 x 240 px) and again the whole iPhone screen without button on the bottom.

screenshotssimulator

xcodeproj

Download the project

Ohh, I would forget. One thing more. Making screenshots on simulator is very quick, unfortunately, it takes few more seconds to render everything on real device.

Animations and transformations

April 26th, 2009

Using right animation / transformation can improve quality of your application. There are most important during game development.

You can use 3 basic transformations – you can marge them or invent your own. These 3 animations are:

  • rotation
  • scale
  • translation

animationsimulator

You can apply animation to almost everything – images (UIImageView), buttons (UIButton), toolbars, label, views…

Rotation:

Let’s say you have an image (UIImageView *myImage) and you would like to do something with it. To rotate it:

myImage.transform = CGAffineTransformMakeRotation(M_PI*0.25);

This will rotate image by 45 degrees right. Here are the typical values and their degree equivalents:

  • M_PI * 0.00 = 0° (original condition)
  • M_PI * 0.25 = 45°
  • M_PI * 0.50 = 90°
  • M_PI * 0.75 = 135°
  • M_PI * 1.00 = 180°
  • M_PI * -0.75 = 225°
  • M_PI * -0.50 = 270°
  • M_PI * -0.25 = 315°

If you would like to use degrees not the range <-1 ; 1> multiplied by M_PI you can define the constant value in header of your class: #define radianConst M_PI/180.0 (right under the #import lines). Now if you want to turn your image by 63 degrees use this:

myImage.transform = CGAffineTransformMakeRotation(63.0*radianConst);

Scale

Scale allows you to zoom in / zoom out your image. It’s more than simple scale, you can set the scale parameter different for image’s width and height. If you would like to have the image 2 times wider and 1.5 times higher try this:

myImage.transform = CGAffineTransformMakeScale(2.0,1.5);

Don’t be surprised that after this operation your image will be pixeled, that’s why in future you should provide images in higher resolution make them smaller on load (for example CGAffineTransformMakeScale(0.5,0.5)) and later when needed show them in original dimensions using CGAffineTransformMakeScale(1.0,1.0).

Scale gives you one more cool effect. If you set scale parameter as a negative value your image will be scaled and flipped horizontally or vertically. Examples:

  • CGAffineTransformMakeScale(-1.0,2.0)image flipped horizontally and twice higher
  • CGAffineTransformMakeScale(1.5,-3.0) – image 1.5 times wider, 3 times higher and flipped vertically
  • CGAffineTransformMakeScale(-2.0,-2.0)image 2 times bigger and flipped both horizontally and vertically

Translation

Translation is doing almost the same thing as changing the center value of any object. It moves image along X and Y axises. It’s the poorest so I will give you only 1 example and won’t talk about this any more.

  • CGAffineTransformMakeTranslation(50.0,20.0)image moved 50 pixels to the right and 20 to the botton

Similar effect you will get using center as I mentioned before: myImage.center = CGPointMake(140.0,90.0). Both of them has their own advantages and disadvantages, but let’s now move to something more interesting.

Animations

You know how to rotate an image (or anything else) by given degree, scale, flip, or move. How about creating a smooth animation? Well it’s very simple:

	[UIView beginAnimations:nil context:NULL];
	[UIView setAnimationDuration:1.5];
	// other animations goes here
	myImage.transform = CGAffineTransformMakeRotation(M_PI*0.5);
	// other animations goes here
	[UIView commitAnimations];

This code will smoothly rotate your object by 45 degress during 1.5 seconds. Please note, that while performing any animation the application continue to run next tasks – doesn’t hold for a given amount of seconds. If you would like your app to do an action when the animation is finished you can use either the NSTimer:

[NSTimer scheduledTimerWithTimeInterval:1.5 target:self selector:@selector(nextMethodName) userInfo:nil repeats:NO];

or extend your animation code by it’s delegate and selector:

	[UIView beginAnimations:nil context:NULL];
	[UIView setAnimationDuration:1.5];
	[UIView setAnimationDelegate:self];
	[UIView setAnimationDidStopSelector:@selector(nextMethodName)];
	myImage.transform = CGAffineTransformMakeRotation(M_PI*0.5);
	// other animations goes here
	[UIView commitAnimations];

Remember that second example is much more efficient, but in that case, nextMethodName (whatever you name it) must return a boolean YES – not void which doesn’t return any value at all. Example:

-(BOOL)nextMethodName {
 	// some tasks
	return YES;
}

Animations can be great to have transitions between two views but I will talk about them in other article.

Two transformations at once

Using any of above transformations you are modyfing only one property – transform of a given object. For example the code:

myImage.transform = CGAffineTransformMakeScale(2.0,1.5);
myImage.transform = CGAffineTransformMakeRotation(M_PI*1.0);

will give you the image rotated by 180 degrees, but won’t be scaled – scale was canceled after making rotation.

If you would like to merge two transform use the following code:

animateLabel.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(2.0, 1.5), CGAffineTransformMakeRotation(M_PI*1.0));

I personally really don’t like this. I didn’t enjoy matrices during Mathematics in school. Any transform creates the matrix and CGAffineTransformConcat is multiplying two matrices – the order is necessary. If you wanted to merge three transforms you would have to use CGAffineTransformConcat merging two transforms as an argument of another CGAffineTransformConcat mergin previous merge with next transform. Sound difficult? Yes, for me two.

Original shape

To conclude, if you would like to quickly cancel any transforms use CGAffineTransformIdentity as a transform property of any object.

myImage.transform = CGAffineTransformIdentity;

I guess it’s all you need to know about transforms and animations right now. Below you can download my project where you can rotate, scale, translate any object using sliders and watch the smooth scale animation.

xcodeproj

Download the project