이번에는 저번 바이오리듬 보다 조금 기능을 추가하여 만들어 보겠습니다. 상단에는 한달 단위로 그래프를 보여주고, 하단에는 해당일의 바이오리듬 정보를 보여 줍니다. 그리고 생일설정을 저장할 수 있도록 하겠습니다. 하단 버튼들의 기능은 아래와 같습니다.

  • << : 한달 전으로 이동
  • < : 하루 전으로 이동
  • > : 하루 후로 이동
  • >> : 한달 후로 이동
  • i : 생일 설정 

한가지 오류가 있습니다. 생일설정 시에 기존의 저장된 날짜가 DatePicker에 설정되지 않습니다. 값은 정확한데 보여지는 부분은 minimumDate로 초기에 선택되어져 있는 것 같습니다. 검색을 해봐도 같은 증상을 겪은 경우는 보았는데, 원인과 해결책은 찾지 못했습니다.


완성된 모습은 아래와 같습니다.

사용자 삽입 이미지


1. 프로젝트 생성
사용자 삽입 이미지
Xcode의 메뉴에서 File/New Project 클릭합니다. iPhone OS의 applicaion에서 좌측과 같은 Unility Application을 선택하고 Choose 버튼을 클릭합니다. 프로젝트 이름에 'iBiorhythm'을 입력한 후에 저장합니다.

Unility Application은 컨텐츠를 보여주는 main view와 설정등을 할 수 있는 flipside view를 제공하며, 두 뷰의 변환시에 아래와 같은 에니메이션 효과를 제공합니다.

사용자 삽입 이미지

사용자 삽입 이미지
Xcode의 Groups & Files를 보시면 좌측과 같이 그룹이 생성되어 있습니다. MainView, FlipsideView 각각에는 view와 view를 관리하는 controller 소스들이 있습니다.


2. 소스코드 
1) Global.h
사용자 삽입 이미지
여러 소스에서 사용할 상수나 변수등을 위해 헤더 파일을 하나 만듭니다. File/New File...을 클릭합니다. Mac OS X에서 C and C++ 항목을 클릭하고 좌측과 같은 Header File이란 아이콘을 선택한 후에 파일명을 Global.h로 입력하고 저장합니다. 생성된 파일에 아래와 같은 내용을 추가 합니다.

#define    MAX_DATATYPE            3

#define US_BIRTHYEAR_KEY        @"birth_year"
#define US_BIRTHMONTH_KEY     @"birth_month"
#define US_BIRTHDAY_KEY          @"birth_day"

#define NM_BIRTHDAYCHANGED   @"NTBirthdayChanged"

#define NAV_PREVMONTH            1
#define NAV_PREVDAY                 2
#define NAV_NEXTDAY                 3
#define NAV_NEXTMONTH            4

#define ONEDAY_SECOND            (24 * 60 * 60)

2) GraphView
그래프를 출력하기 위한 View를 생성합니다. 메뉴에서 File/New File...을 클릭 후에 iPhone OS/Cocoa Touch Classes 항목에서 UIView subclass를 선택한 후에 Next 버튼을 클릭합니다. 파일명에 GraphView.m을 입력한 후에 저장합니다.

* GraphView.h
#import <UIKit/UIKit.h>

@interface GraphView : UIView {
    UIImage                *backgroundImage;
   
    NSDateComponents    *currentDate;
    NSDateComponents    *birthDate;
   
    float biorhythmData[31][MAX_DATATYPE];
}

@property (nonatomic, retain) NSDateComponents *currentDate;
@property (nonatomic, retain) NSDateComponents *birthDate;

- (void)setBirthDate:(int)year atMonth:(int)month atDay:(int)day;
- (void)setCurrentDate:(int)year atMonth:(int)month atDay:(int)day;
- (void)changeDate:(int)pos;

- (NSDateComponents *)currentDate;

- (void)resetBiorhythmData;
- (float *)getBiorhythmDayData:(int)day;

@end

* GraphView.m
#import "Global.h"
#import "GraphView.h"

@implementation GraphView

@synthesize currentDate;
@synthesize birthDate;

- (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
    }
    return self;
}

- (void)awakeFromNib {
    currentDate = [[NSDateComponents alloc] init];
   
    /* 배경 이미지로 사용할 bio.png 로드 */
    backgroundImage = [[UIImage alloc] initWithContentsOfFile:
                       [[NSBundle mainBundle] pathForResource:@"bio" ofType:@"png"]];
}

- (void)drawRect:(CGRect)rect {
#define START_X            17    // 그래프 시작 X 위치
#define START_Y            22  // 그래프 시작 Y 위치
#define DAY_WIDTH        10  // 일별 간격
#define BOTTOM_HEIGHT    4   // 하단 공백 높이

    /* 데이터별 색상 테이블 */
    static const CGFloat color[MAX_DATATYPE][5] = {
        { 1.0, 0.0, 0.0, 1.0 }, //red
        { 0.0, 1.0, 0.0, 1.0 }, //green
        { 0.0, 0.0, 1.0, 1.0 }  //blue
    };
   
    CGSize viewSize = rect.size;
    CGContextRef context = UIGraphicsGetCurrentContext();

    /* 배경 이미지 출력 */
    CGPoint point;
    point.x = point.y = 0;
    [backgroundImage drawAtPoint:point];

    /* 오늘 날짜를 가르키는 위치는 노란 선으로 출력 */
    CGContextBeginPath(context);
    CGContextSetLineWidth(context, 1.0);
    CGContextSetRGBStrokeColor(context, 1.0, 0.8, 0.0, 1.0);
    CGContextMoveToPoint(context, START_X + (DAY_WIDTH * ([currentDate day] - 1)), START_Y);
    CGContextAddLineToPoint(context, START_X + (DAY_WIDTH * ([currentDate day] - 1)), viewSize.height - BOTTOM_HEIGHT);
    CGContextStrokePath(context);
   
    /* 바이오리듬 출력 */
    int i, j;
    int lineX = START_X;
   
    int vcenter = START_Y + (viewSize.height - START_Y - BOTTOM_HEIGHT)/2; // 그래프의 Y 중간 좌표
   
    /* 바이오리듬 출력 */
    for (i = 1; i < 31; i++) {
        for(j = 0; j < MAX_DATATYPE; j++) {
            CGContextBeginPath(context);
            CGContextSetLineWidth(context, 2.0);
           
            CGContextSetStrokeColor(context, color[j]);
           
            CGContextMoveToPoint(context, lineX, vcenter - biorhythmData[i-1][j]);
            CGContextAddLineToPoint(context, lineX + DAY_WIDTH, vcenter - biorhythmData[i][j]);
            CGContextStrokePath(context);
        }
        lineX += DAY_WIDTH;
    }
}

- (void)dealloc {
    [backgroundImage release];
    [currentDate release];
    [super dealloc];
}

- (void)resetBiorhythmData {
    static const double s_values[MAX_DATATYPE] = {
        23.0, 33.0, 28.0 // 신체, 지성, 감성
    };
   
    /* 현재 설정된 날짜의 1일 부터 생일 사이의 날수를 구한다. */
    NSDateComponents *firstDate = [[NSDateComponents alloc] init];
    [firstDate setYear: [currentDate year]];
    [firstDate setMonth: [currentDate month]];
    [firstDate setDay:1];
   
    NSDate *tempDay = [[NSCalendar currentCalendar] dateFromComponents:firstDate];
    NSDate *birthDay = [[NSCalendar currentCalendar] dateFromComponents:birthDate];
   
    NSTimeInterval ti = [tempDay timeIntervalSinceDate:birthDay];
    [firstDate release];

    /* 바이오리듬 데이터를 구한다 */
    double days = ceil(fabs(ti) / ONEDAY_SECOND);
   
    for (int i = 0; i < 31; i++) {
        for(int j = 0; j < MAX_DATATYPE; j++) {
            biorhythmData[i][j] = sin((days/s_values[j]) * 2 * 3.14195) * 100;
        }
        days += 1.0;
    }
   
    /* 변경된 데이터로 다시 그림 */
    [self setNeedsDisplay];
}

/* 현재 날짜 변경 */
- (void)setCurrentDate:(int)year atMonth:(int)month atDay:(int)day {
    [currentDate setYear:year];
    [currentDate setMonth:month];
    [currentDate setDay:day];
}

/* 생일 변경 */
- (void)setBirthDate:(int)year atMonth:(int)month atDay:(int)day {
    [birthDate setYear:year];
    [birthDate setMonth:month];
    [birthDate setDay:day];
   
    NSLog(@"SET BIRTHDAY: %d, %d, %d", year, month, day);
}

/* 사용자가 버튼을 클릭했을 때, 날짜를 변경한다 */
- (void)changeDate:(int)pos {
    NSRange monthRange;
   
    NSTimeInterval newValue = 0;
    NSDate *tempDate = [[NSCalendar currentCalendar] dateFromComponents:currentDate];
   
    if (pos == NAV_PREVMONTH) {
        /* 이전 달로 이동을 위해 이전 달의 날수를 구한다 */
        NSDateComponents *prevMonthDate = [[NSDateComponents alloc] init];
        [prevMonthDate setYear: [currentDate year]];
        [prevMonthDate setMonth: [currentDate month]-1];
        [prevMonthDate setDay:1];
       
        monthRange = [[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit
                    inUnit:NSMonthCalendarUnit
                    forDate:[[NSCalendar currentCalendar] dateFromComponents:prevMonthDate]];
        [prevMonthDate release];
       
        /* 한달 전으로 이동 */
        newValue = ONEDAY_SECOND * monthRange.length;
        newValue = -newValue;
    }
    else if (pos == NAV_PREVDAY) {
        /* 하루 전으로 이동 */
        newValue = -ONEDAY_SECOND;
    }   
    else if (pos == NAV_NEXTDAY) {
        /* 하루 후로 이동 */
        newValue = ONEDAY_SECOND;
    }   
    else if (pos == NAV_NEXTMONTH) {
        /* 다음 달로 이동을 위해 현재 달의 날수를 구한다 */
        monthRange = [[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit
                                                        inUnit:NSMonthCalendarUnit
                                                       forDate:[[NSCalendar currentCalendar] dateFromComponents:currentDate]];
        /* 한달 후로 이동 */
        newValue = ONEDAY_SECOND * monthRange.length;
    }

    /* 날짜를 변경하고 변경된 날짜를 구한다. */
    NSDateComponents *newDate = [[NSCalendar currentCalendar] components:(NSYearCalendarUnit | NSMonthCalendarUnit |  NSDayCalendarUnit)
                                                            fromDate:[tempDate addTimeInterval:newValue]];
   
    /* 현재 날짜를 변경 */
    [currentDate setYear: [newDate year]];
    [currentDate setMonth: [newDate month]];
    [currentDate setDay: [newDate day]];
   
    /* 바이오리듬을 다시 계산하고 출력한다. */
    [self resetBiorhythmData];
}

- (float *)getBiorhythmDayData:(int)day {
    return biorhythmData[day-1];
}

- (NSDateComponents *)currentDate {
    return currentDate;
}
@end


3) MainView
* MainView.h
#import <UIKit/UIKit.h>

@interface MainView : UIView {
    UIImage     *barFrame[MAX_DATATYPE];
    double        bioData[MAX_DATATYPE];
}

-(void)setData:(int)value1 secondValue:(int)value2 thirdValue:(int)value2;

@end

* MainView.m
#import "Global.h"
#import "MainView.h"

@implementation MainView

- (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
    }
    return self;
}

- (void)awakeFromNib {
    /* 바의 배경을 위한 이미지를 로드한다. */
    for (int i = 1; i <= MAX_DATATYPE; i++) {
        barFrame[i - 1] = [[UIImage alloc] initWithContentsOfFile:
         [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"frame%d", i] ofType:@"png"]];
    }
}

- (void)drawRect:(CGRect)rect {
#define CENTER_X        210  // 바의 중간(0) 위치
#define START_Y            300  // 바가 출력될 Y 위치
#define START_X            120  // 바가 출력될 X 위치
#define BAR_HEIGHT        14.0 // 바의 높이   
#define    BAR_SPACE        40   // 각 바간 간격
   
    // Drawing code
    CGContextRef context = UIGraphicsGetCurrentContext();
   
    int y = START_Y + 15;
    CGPoint point;
   
    point.y = START_Y;
    point.x = START_X;
   
    for (int i = 0; i < MAX_DATATYPE; i++) {
        /* 배경 이미지 출력 */
        [barFrame[i] drawAtPoint:point];
       
        CGContextBeginPath(context);
        CGContextSetLineWidth(context, BAR_HEIGHT);
       
        /* 바이오리듬 값이 0보다 작으면 붉은 색으로 크면 파란색으로 표시 */
        if (bioData[i] > 0)
            CGContextSetRGBStrokeColor(context, 0.0, 0.0, 1.0, 1.0);
        else
            CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
       
        /* 바를 그린다 */
        CGContextMoveToPoint(context, CENTER_X, y);
        CGContextAddLineToPoint(context, CENTER_X + (bioData[i] * 80/100) , y);
       
        CGContextStrokePath(context);

        /* Y 좌표 변경 */
        point.y += BAR_SPACE;
        y += BAR_SPACE;
    }
}

-(void)setData:(int)value1 secondValue:(int)value2 thirdValue:(int)value3 {
    bioData[0] = value1;
    bioData[1] = value2;
    bioData[2] = value3;
}

- (void)dealloc {
    for (int i = 0; i < MAX_DATATYPE; i++) {
        [barFrame[i] release];
    }
   
    [super dealloc];
}

@end


4) MainViewController
* MainViewController.h
#import <UIKit/UIKit.h>

@class GraphView;
@class MainView;

@interface MainViewController : UIViewController {
    IBOutlet MainView        *mainView;   
    IBOutlet GraphView      *graphView;
    IBOutlet UITextField    *value_1;
    IBOutlet UITextField    *value_2;
    IBOutlet UITextField    *value_3;
   
    IBOutlet UIBarButtonItem *currentDate;
}

- (void)updateInfoUI;
- (void)birthdayChanged:(NSNotification *)note;

- (IBAction)navigationButtonClicked:(id)sender;

@end

* MainViewController.m
#import "Global.h"
#import "MainViewController.h"
#import "MainView.h"
#import "GraphView.h"

@implementation MainViewController

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

- (void)viewDidLoad {
    /* 오늘 날짜를 구한다. */
    NSDateComponents *today = [[NSCalendar currentCalendar]
                       components:(NSYearCalendarUnit | NSMonthCalendarUnit |  NSDayCalendarUnit)
                       fromDate:[NSDate date]];
   
    /* 현재 날짜 설정 */
    [graphView setCurrentDate:[today year]
                      atMonth:[today month]
                        atDay:[today day]];

    /* 생년월일 설정 */
    [graphView setBirthDate:[[NSUserDefaults standardUserDefaults] integerForKey:US_BIRTHYEAR_KEY]
                      atMonth:[[NSUserDefaults standardUserDefaults] integerForKey:US_BIRTHMONTH_KEY]
                        atDay:[[NSUserDefaults standardUserDefaults] integerForKey:US_BIRTHDAY_KEY]];

    /* 그래프를 그리고 UI를 설정한다. */
    [graphView resetBiorhythmData];
    [self updateInfoUI];
   
    /* 환경설정에서 생일이 변경되었을 때를 위해 옵저버로 등록 */
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(birthdayChanged:)
                                                 name:NM_BIRTHDAYCHANGED
                                               object:nil];
}

- (void)updateInfoUI {
    /* 하단 타이틀에 현재 날짜를 출력 */
    NSDateComponents *date = [graphView currentDate];
    [currentDate setTitle:[NSString stringWithFormat:@"%d.%02d.%02d",
                           [date year], [date month], [date day]]];
   
    float *bioValue = [graphView getBiorhythmDayData:[date day]];
    int value1 = (int)ceil(bioValue[0]);
    int value2 = (int)ceil(bioValue[1]);
    int value3 = (int)ceil(bioValue[2]);
   
    /* 각 바이오리듬 값 출력 */
    [value_1 setText:[NSString stringWithFormat:@"%d", value1]];
    [value_2 setText:[NSString stringWithFormat:@"%d", value2]];
    [value_3 setText:[NSString stringWithFormat:@"%d", value3]];
   
    /* mainView에 현재 설정된 값을 알려주고, 바가 다시 그려지도록 한다. */
    [mainView setData:value1 secondValue:value2 thirdValue:value3];
    [mainView setNeedsDisplay];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
    // Release anything that's not essential, such as cached data
}

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

- (void)birthdayChanged:(NSNotification *)note
{
    /* 생일이 변경되었을 때 다시 설정하고 그려지도록 한다. */
    [graphView setBirthDate:[[NSUserDefaults standardUserDefaults] integerForKey:US_BIRTHYEAR_KEY]
                      atMonth:[[NSUserDefaults standardUserDefaults] integerForKey:US_BIRTHMONTH_KEY]
                        atDay:[[NSUserDefaults standardUserDefaults] integerForKey:US_BIRTHDAY_KEY]];
   
    [graphView resetBiorhythmData];
    [self updateInfoUI];
}       

- (IBAction) navigationButtonClicked:(id)sender {
    /* 사용자가 날짜 이동 버튼을 클릭하였을 경우 처리 */
    [graphView changeDate:[sender tag]];
    [self updateInfoUI];
}

@end


5) FlipsideViewController
* FlipsideViewController.h
#import <UIKit/UIKit.h>

@interface FlipsideViewController : UIViewController {
    IBOutlet UIDatePicker    *datePicker;
}

@property (nonatomic, retain) UIDatePicker *datePicker;

@end

* FlipsideViewController.m
#import "FlipsideViewController.h"

@implementation FlipsideViewController

@synthesize datePicker;

- (void)viewDidLoad {
    self.view.backgroundColor = [UIColor viewFlipsideBackgroundColor];       
 }

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
    // Release anything that's not essential, such as cached data
}

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

@end


5) RootViewController
* RootViewController.m
#import "Global.h"
#import "RootViewController.h"
#import "MainViewController.h"
#import "FlipsideViewController.h"

@implementation RootViewController

@synthesize infoButton;
@synthesize flipsideNavigationBar;
@synthesize mainViewController;
@synthesize flipsideViewController;

- (void)viewDidLoad {
   
    MainViewController *viewController = [[MainViewController alloc] initWithNibName:@"MainView" bundle:nil];
    self.mainViewController = viewController;
    [viewController release];
   
    [self.view insertSubview:mainViewController.view belowSubview:infoButton];
}

- (void)loadFlipsideViewController {
   
    FlipsideViewController *viewController = [[FlipsideViewController alloc] initWithNibName:@"FlipsideView" bundle:nil];
    self.flipsideViewController = viewController;
    [viewController release];
   
    // Set up the navigation bar
   
    UINavigationBar *aNavigationBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 44.0)];
    aNavigationBar.barStyle = UIBarStyleBlackOpaque;
    self.flipsideNavigationBar = aNavigationBar;
    [aNavigationBar release];
   
    UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(toggleView)];
    UINavigationItem *navigationItem = [[UINavigationItem alloc] initWithTitle:@"iBiorhythm"];
    navigationItem.rightBarButtonItem = buttonItem;
    [flipsideNavigationBar pushNavigationItem:navigationItem animated:NO];
    [navigationItem release];
    [buttonItem release];
}

- (IBAction)toggleView {   
    if (flipsideViewController == nil) {
        [self loadFlipsideViewController];
    }
   
    UIView *mainView = mainViewController.view;
    UIView *flipsideView = flipsideViewController.view;
   
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:1];
    [UIView setAnimationTransition:([mainView superview] ? UIViewAnimationTransitionFlipFromRight : UIViewAnimationTransitionFlipFromLeft) forView:self.view cache:YES];
   
    if ([mainView superview] != nil) {
        [flipsideViewController viewWillAppear:YES];
        [mainViewController viewWillDisappear:YES];
        [mainView removeFromSuperview];
        [infoButton removeFromSuperview];
        [self.view addSubview:flipsideView];
        [self.view insertSubview:flipsideNavigationBar aboveSubview:flipsideView];
        [mainViewController viewDidDisappear:YES];
        [flipsideViewController viewDidAppear:YES];
       
        /* DataPicker 날짜를 저장된 날짜로 설정 (현재 동작하지 않음) */
        NSDateComponents *curDate = [[NSDateComponents alloc] init];
       
        int year = [[NSUserDefaults standardUserDefaults] integerForKey:@"birth_year"];
        int month = [[NSUserDefaults standardUserDefaults] integerForKey:@"birth_year"];
        int day = [[NSUserDefaults standardUserDefaults] integerForKey:@"birth_year"];

        [curDate setYear: year];
        [curDate setMonth: month];
        [curDate setDay: day];
   
        NSDate *tempDate = [[NSCalendar currentCalendar] dateFromComponents:curDate];
        [[flipsideViewController datePicker] setDate:tempDate animated:YES];
           
        [curDate release];
    } else {
        [mainViewController viewWillAppear:YES];
        [flipsideViewController viewWillDisappear:YES];
        [flipsideView removeFromSuperview];
        [flipsideNavigationBar removeFromSuperview];
        [self.view addSubview:mainView];
        [self.view insertSubview:infoButton aboveSubview:mainViewController.view];
        [flipsideViewController viewDidDisappear:YES];
        [mainViewController viewDidAppear:YES];
       
        /* 사용자가 설정한 날짜(생일)를 저장 한다 */
        NSDateComponents *newDate = [[NSCalendar currentCalendar]
              components:(NSYearCalendarUnit | NSMonthCalendarUnit |  NSDayCalendarUnit)
              fromDate:[[flipsideViewController datePicker] date]];
       
        [[NSUserDefaults standardUserDefaults] setInteger:[newDate year] forKey:US_BIRTHYEAR_KEY];
        [[NSUserDefaults standardUserDefaults] setInteger:[newDate month] forKey:US_BIRTHMONTH_KEY];
        [[NSUserDefaults standardUserDefaults] setInteger:[newDate day] forKey:US_BIRTHDAY_KEY];
       
        /* 바이오리듬이 변경되도록 메시지를 보낸다. */
        [[NSNotificationCenter defaultCenter]
                   postNotificationName:NM_BIRTHDAYCHANGED
                   object:self];
    }
    [UIView commitAnimations];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
    // Release anything that's not essential, such as cached data
}

- (void)dealloc {
    [infoButton release];
    [flipsideNavigationBar release];
    [mainViewController release];
    [flipsideViewController release];
    [super dealloc];
}

@end

다음 포스팅에선 인터페이스 빌더에서 작업을 하고 어플리케이션이 동작하도록 완성시켜 보겠습니다. 동작은 하는데 제대로 만든 것인지는 모르겠습니다.