《iOS6 application development》学习之路:No.3: 自定义选择器

《iOS6 application development》学习之路:No.3: 自定义选择器《iOS6 application development》学习之路:No.3: 自定义选择器

先看下程序跑起来的样子吧,没有做任何,任何界面上的优化,所以请忽视丑陋的页面。

总共就两个,第一个页面是inital页面,中间一个label用来显示用户做的不同选择,下方是一个放在tool bar里面的button,用户点击后可以进行两个页面的切换。第2个页面总共有3个空间,最上端是一个自定义的pick view,两列,第一列显示动物的图片,第二列显示动物的叫声。中间是一个label,最下端是一个按钮,点击按钮后用来返回上一个页面。

因为教材中是按照功能拆分了一块一块讲解的,所以这次分别把2个页面的最终接口和实现文件整体来看,也算是给自己做个回顾和总结。

前提:

需要在Xcode的项目导航器中新增一个类,名字叫AnimalChooserViewController。然后将这个类和我们新增加的页面关联起来,从类的名字也能看出来是第2个页面了。记得把两个页面在IB中的名字分别改成initial和Anima Chooser,这样做的好处就不多说了。

还要把一些资源文件拖进来,主要是7个动物的图片,这些都可以在这本书的网站上下载到。好了,下面就是4个重头戏了。

先来说initial页面的接口文件:ViewControll.h

#import <UIKit/UIKit.h>
#import "AnimalChooserViewController.h"

@interface ViewController : UIViewController

@property (weak, nonatomic) IBOutlet UILabel *outputLabel;

- (IBAction)showAnimalChosser:(id)sender;

- (void) displayAnimal:(NSString* )chosenAnimal
             withSound:(NSString* )chosenSound
         fromComponent:(NSString* )chosenComponent;

@property (nonatomic) Boolean animalChooserVivible;

@end

很简单有没有,

这里面我们要import进来我们新建的那个类,因为要做交互啊。一个控件 outputLabel 和函数 showAnimalChosser 都是很简单的,通过IB里面鼠标拖动来添加,这些都再简单不过了。我们需要一个新的property: animalChooserVisibile来存储第2个场景的当前可见性。同时还有一个自定义的函数,displayAnimal,有3个参数:chosenAnima、chosenSound和chosenComponent,这个函数是用来接收第2个场景传递过来的信息并显示在label上的。

下面上ViewControll.m文件:

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)showAnimalChosser:(id)sender {
    if(self.animalChooserVivible !=YES){
        [self performSegueWithIdentifier:@"toAnimalChooser" sender:sender]; //preform the animal chooser sence
        self.animalChooserVivible = YES;
    }
}

- (void) displayAnimal:(NSString *)chosenAnimal withSound:(NSString *)chosenSound fromComponent:(NSString *)chosenComponent{
    NSString* animalSoundString;
    animalSoundString = [[NSString alloc]
                         initWithFormat:@"You changed %@ (%@ and the sound %@)",
                         chosenComponent,chosenAnimal,chosenSound];
    self.outputLabel.text = animalSoundString;
}


- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    ((AnimalChooserViewController *) segue.destinationViewController).delegate = self;
}

@end

实现文件主要完成3个函数:showAnimalChooser是用户点击toolbar上的按钮时进行的函数:判断当第2个场景可显示时,进行切换。注意,一定要在IB中把场景切换命名为toAnimaChooser,否则是无法完成手动切换的。

displayAnimal函数根绝接受到的3个参数,修改label的文字内容内容,这个函数会在第2个界面上点击Done之后被调用。

prepareForSeque函数:为了使用属性delegate来访问initial场景,我们马上将看到在AnimalChooserViewController的接口文件中会加入属性,所以在这里调用此函数来设置initial场景的属性。

AnimalChooserViewController.h:

#import <UIKit/UIKit.h>
#import "ViewController.h"
@class ViewController;

@interface AnimalChooserViewController : UIViewController <UIPickerViewDataSource, UIPickerViewDelegate>

@property (weak, nonatomic) id delegate;

- (IBAction)dismissAnimalChosser:(id)sender;
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender;
@end

@interface AnimalChooserViewController(){
    NSArray* _animalNames;
    NSArray* _animalSounds;
    NSArray* _animalImages;
}
@end
看到了新添的属性 id delegate,用来访问initial场景。

一个函数: dismissAnimalChooser,当用户点击DONE按钮时,退回到initial界面。

这里需要将3个数组声名为私有变量,OC的私有变量是这样声名的,记一下了。

为什么要声名3个数组是为了更好的现实动物的名字,因为原始的图片是带了.png的,我们当然不希望把这个现实出来,所以要重新映射一下名字,以及声音和图片。

注意到在interface AnimalChooserViewController: UIViewController 后面还加上了  <UIPickerViewDataSource, UIPickerViewDelegate>,作用是为了调用自定义选择器的几个函数。在实现文件中会用到的。

AnimalChooserViewController.m:


#import "AnimalChooserViewController.h"
#define kComponentCount 2           //define how many columns will be performed in the pick view
#define kAnimalComponent 0          //the index for the first column
#define kSoundComponent 1           //index for the second column

@interface AnimalChooserViewController ()

@end

@implementation AnimalChooserViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    _animalNames = @[@"Mouse", @"Goose", @"Cat",@"Dog",@"Snake",@"Bear",@"Pig"];
    _animalSounds = @[@"Oink",@"Rawr",@"Ssss",@"Roof",@"Meow",@"Honk",@"Squeak"];
    _animalImages = @[
                      [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"mouse.png"]],
                      [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"goose.png"]],
                      [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"cat.png"]],
                      [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"dog.png"]],
                      [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"snake.png"]],
                      [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"bear.png"]],
                      [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"pig.png"]],
                      ];
}

- (void) viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    //Do any additional after view has been displayed
    
    ViewController* initialView;
    initialView = (ViewController*)self.delegate;
    [initialView displayAnimal:_animalNames[0] withSound:_animalSounds[0] fromComponent:@"nothing yet be chooosen"];
}


- (void)viewWillDisappear:(BOOL)animated{
    ((ViewController *)self.delegate).animalChooserVivible = NO;
}



- (NSInteger) numberOfComponentsInPickerView:(UIPickerView *)pickerView {
    return kComponentCount;
}

//founctions in the protocol "UIPickerViewDataSource"
- (NSInteger) pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
    if(component == kAnimalComponent){
        return [_animalNames count];
    }else {
        return [_animalSounds count];
    }
}

//-----------------------------------------------------------------------//
// founctions in the protocol "UIPickerViewDelegate"
- (UIView* ) pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view{
    if (component == kAnimalComponent){
        return _animalImages[row];
    } else {
        UILabel* soundLabel;
        soundLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 32)];
        soundLabel.backgroundColor = [UIColor clearColor];// make the Rect into the background, because the color is transparent
        soundLabel.text = _animalSounds[row];
        return soundLabel;
    }
}

//set the height of one single row
- (CGFloat) pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component{
    return 55.0;
}

//set the width of the diffenret column
- (CGFloat) pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component{
    if(component == kAnimalComponent){
        return 75.0;
    }else {
        return 150.0;
    }
}

//after user selected one row
- (void) pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
    ViewController* initialView;
    initialView = (ViewController* )self.delegate;
    
    if(component == kAnimalComponent){
        int chosenSound = [pickerView selectedRowInComponent:kSoundComponent];
        [initialView displayAnimal:_animalNames[row] withSound:_animalSounds[chosenSound] fromComponent:@"the Animal"];
    }
    else{
        int chosenAnimal = [pickerView selectedRowInComponent:kAnimalComponent];
        [initialView displayAnimal:_animalNames[chosenAnimal] withSound:_animalSounds[row] fromComponent:@"the Sound"];
    }
}

//-----------------------------------------------------------------------//




- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

- (IBAction)dismissAnimalChosser:(id)sender {
    [self dismissViewControllerAnimated:YES completion:nil]; // manual close this sence.
}
@end

实现文件有点长,不要急,分开来看行了。

首先#define了几个名称,我都用注释标出来了。

在ViewDidLoad函数中,初始化了3个私有数组,

viewDidAppear函数中,初始化界面,把选择器都设置成第0帐图片和第0个声音,然后文字设置成 nothing yet been chosen。其实就是默认的选择器状态。注意这里其实就已经调用了initial界面的函数, 如果用户没有做任何改变返回第一个界面,可以给用户正确的提示。

viewWillDisappear

将initial用到的属性设置成NO,

 numberOfComponentsInPickerView

是我们在头文件中看到了<>里面包含的协议里面的函数,这个方法返回选择器将显示几个组件,我们这里就两列,数字已经#define过了

//founctions in the protocol "UIPickerViewDataSource"

- (NSInteger) pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{

    if(component == kAnimalComponent){

        return [_animalNames count];

    }else {

        return [_animalSounds count];

    }

}


我标注出来了,在协议  datasource中有的方法,numberOfRowsInComponent,返回每个组件包含的元素数目。数组的数目可以用count函数得到。

// founctions in the protocol "UIPickerViewDelegate"
- (UIView* ) pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view{
    if (component == kAnimalComponent){
        return _animalImages[row];
    } else {
        UILabel* soundLabel;
        soundLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 32)];
        soundLabel.backgroundColor = [UIColor clearColor];// make the Rect into the background, because the color is transparent
        soundLabel.text = _animalSounds[row];
        return soundLabel;
    }
}

//set the height of one single row
- (CGFloat) pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component{
    return 55.0;
}

//set the width of the diffenret column
- (CGFloat) pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component{
    if(component == kAnimalComponent){
        return 75.0;
    }else {
        return 150.0;
    }
}

//after user selected one row
- (void) pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
    ViewController* initialView;
    initialView = (ViewController* )self.delegate;
    
    if(component == kAnimalComponent){
        int chosenSound = [pickerView selectedRowInComponent:kSoundComponent];
        [initialView displayAnimal:_animalNames[row] withSound:_animalSounds[chosenSound] fromComponent:@"the Animal"];
    }
    else{
        int chosenAnimal = [pickerView selectedRowInComponent:kAnimalComponent];
        [initialView displayAnimal:_animalNames[chosenAnimal] withSound:_animalSounds[row] fromComponent:@"the Sound"];
    }
}

//-----------------------------------------------------------------------//

这几个函数都是DateViewDelegate协议中有的方法。当你在头文件中包含了这个协议,在接口文件中编译器会自动补全你想要写的函数的,good UE!

第1个函数给每个选择器元素提供自定义视图,这里手动动态添加了label,同时必须有2个的return值,否则XCode会提示你错误。这里注意到label的background用的时 clearColor,返回一个透明的颜色对象,否则矩形就不会融合到选择器视图的背景中。

第2个函数设置了每行的高度

第3个函数设置了不同列的宽度,这些数字都可以通过不断的纠错来达到你最满意的效果,话说回来可能是程序比较小,总感觉iOS的系统部署起来非常快,比android快多了。。。。

最后一个函数是在用户做出选择时的响应,其实就是给要传递给initial页面的3个参数附上正确的值。


实现文件的最后一个函数


- (IBAction)dismissAnimalChosser:(id)sender {

    [self dismissViewControllerAnimated:YES completion:nil]; // manual close this sence.

}

@end

因为是iPhone的版本,需要手工关闭场景,如果是iPad,因为弹出来的是个对话框,所以点击其他地方就可以关闭了,所以这个函数只有在iPhone上才需要用到。













上一篇:如何让类对象只在栈(堆)上分配空间?


下一篇:IOS开发-KVC