Archive for 五月, 2011

Cocoa notes (18) Core Data

星期四, 五月 26th, 2011

这是一个比较重要的章节. Core Data是一个数据框架,主要目的是自动化管理数据,脱离实际的数据引擎接口,用统一的方式操作数据,比较类似于java中的hibernate?

* 要使用core data,需要#import

准备环境(初始化model, coordinator, context)

NSManagedObjectModel 用来描述应用中的数据模型(entities).

创建一个NSManagedObjectModel

1
2
// 将合并所有在给出bundles参数内的model,如果mergedModelFromBundles传入nil则自动读取main bundle的
NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];

NSPersistentStoreCoordinator 是永久存储介质(关系数据库)和managed object context中间的联系层,context必须有coordinator设置,否则就是不能工作的context。

1
2
3
4
// 初始化
NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedObjectModel];
// 设置数据库类型,数据库url等
[persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error]

NSManagedObjectContext 用于管理managed object

1
2
self.context = [[[NSManagedObjectContext alloc] init] autorelease];
[self.context setPersistentStoreCoordinator:persistentStoreCoordinator];

以上过程合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
        NSError *error;
    NSURL *url = [NSURL fileURLWithPath:[NSHomeDirectory() stringByAppendingString:@"/Documents/cdintro_00.sqlite"]];
   
    // Init the model, coordinator, context
    NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedObjectModel];
    if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error])
        NSLog(@"Error: %@", [error localizedDescription]);
    else
    {
        self.context = [[[NSManagedObjectContext alloc] init] autorelease];
        [self.context setPersistentStoreCoordinator:persistentStoreCoordinator];
    }
    [persistentStoreCoordinator release];

保存数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    // Insert objects for department and two people, setting their properties
    Department *department = (Department *)[NSEntityDescription insertNewObjectForEntityForName:@"Department" inManagedObjectContext:self.context];
    department.groupName = @"Office of Personnel Management";
   
    Person *person1 = (Person *)[NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:self.context];
    person1.name = @"John Smith";
    person1.birthday = [self dateFromString:@"12-1-1901"];
    person1.department = department;
   
    Person *person2 = (Person *)[NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:self.context];
    person2.name = @"Jane Doe";
    person2.birthday = [self dateFromString:@"4-13-1922"];
    person2.department = department;
   
    department.manager = person1;
    department.members = [NSSet setWithObjects:person1, person2, nil];
   
    // Save the data
    NSError *error;
    if (![self.context save:&error]) NSLog(@"Error: %@", [error localizedDescription]);

NSEntityDescription 用来描述core data中的一个entity(对于数据库来说是一个表,对于代码来说是一个类)

NSManagedObject创建的另外一个方法
– initWithEntity:insertIntoManagedObjectContext:
会触发awakeFromInsert ,类似的方法同样还有awakeFromFetch

1
2
3
4
5
- (void) awakeFromInsert
{
    [super awakeFromInsert];
    [self setCreationDate:[NSDate date]];
}

查询数据

NSFetchRequest用于描述一个查询中的参数(条件,排序,结果数等等)。
NSSortDescriptor 用于描述查询的排序方式,并可以提供自定义的排序比较方法。
NSFetchedResultsController用于管理查询返回结果并提供给UITableView。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void) fetchObjects
{
    // 创建Request
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    [fetchRequest setEntity:[NSEntityDescription entityForName:@"Person" inManagedObjectContext:self.context]];
   
    // 添加一个排序描述
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES selector:nil];
    NSArray *descriptors = [NSArray arrayWithObject:sortDescriptor];
    [fetchRequest setSortDescriptors:descriptors];
    [sortDescriptor release];
   
    // 初始化results controller,cache叫Root
    NSError *error;
    self.results = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.context sectionNameKeyPath:nil cacheName:@"Root"];
    self.results.delegate = self;
        // 查询
    if (![[self results] performFetch:&error])  NSLog(@"Error: %@", [error localizedDescription]);

    [self.results release];
    [fetchRequest release];
}

创建fetch request 模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
NSManagedObjectModel *model = <#Get a model#>;
NSFetchRequest *requestTemplate = [[NSFetchRequest alloc] init];
NSEntityDescription *publicationEntity =
    [[model entitiesByName] objectForKey:@"Publication"];
[requestTemplate setEntity:publicationEntity];
 
NSPredicate *predicateTemplate = [NSPredicate predicateWithFormat:
    @"(mainAuthor.firstName like[cd] $FIRST_NAME) AND \
        (mainAuthor.lastName like[cd] $LAST_NAME) AND \
        (publicationDate > $DATE)"
];
[requestTemplate setPredicate:predicateTemplate];
 
[model setFetchRequestTemplate:requestTemplate
    forName:@"PublicationsForAuthorSinceDate"];
[requestTemplate release];

获取模板并查询

1
2
3
4
5
6
7
8
9
10
NSManagedObjectModel *model = <#Get a model#>;
NSError *error = nil;
NSDictionary *substitutionDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
    @"Fiona", @"FIRST_NAME", @"Verde", @"LAST_NAME",
    [NSDate dateWithTimeIntervalSinceNow:-31356000], @"DATE", nil];
NSFetchRequest *fetchRequest =
    [model fetchRequestFromTemplateWithName:@"PublicationsForAuthorSinceDate"
            substitutionVariables:substitutionDictionary];
NSArray *results =
    [aManagedObjectContext executeFetchRequest:fetchRequest error:&error];

* 如果模板中没有可替换的参数,你必须用以下两种方法之一
Use fetchRequestFromTemplateWithName:substitutionVariables: and pass nil as the variables argument; or
Use fetchRequestTemplateForName: and copy the result.
If you try to use the fetch request returned by fetchRequestTemplateForName:, this generates an exception (“Can’t modify a named fetch request in an immutable model”).

NSFetchedResultsControllerDelegate 协议,当results controller的结果集增加,修改,删除记录时会触发这些代理方法
– controllerWillChangeContent:
– controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:
– controller:didChangeSection:atIndex:forChangeType:
– controllerDidChangeContent:

NSFetchedResultsController和UITableView的结合
fetch时设置sectionNameKeyPath,用于在table view中显示index title

1
self.results = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.context sectionNameKeyPath:@"section" cacheName:@"Root"];

以下是table view相关的delegate方法,其中
tableView:titleForHeaderInSection:
sectionIndexTitlesForTableView:
和设置index title有关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Retrieve or create a cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"basic cell"];
    if (!cell) cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"basic cell"] autorelease];
   
    // Recover object from fetched results
    NSManagedObject *managedObject = [self.results objectAtIndexPath:indexPath];
    cell.textLabel.text = [managedObject valueForKey:@"name"];
    UIColor *color = [self getColor:[managedObject valueForKey:@"color"]];
    cell.textLabel.textColor = ([[managedObject valueForKey:@"color"] hasPrefix:@"FFFFFF"]) ? [UIColor blackColor] : color;
   
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // When a row is selected, color the navigation bar accordingly
    NSManagedObject *managedObject = [self.results objectAtIndexPath:indexPath];
    UIColor *color = [self getColor:[managedObject valueForKey:@"color"]];
    self.navigationController.navigationBar.tintColor = color;
}

#pragma mark Sections
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Use the fetched results section count
    return [[self.results sections] count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return  the count for each section
    return [[[self.results sections] objectAtIndex:section] numberOfObjects];
}

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)aTableView
{
    // Return the array of section index titles
    return self.results.sectionIndexTitles;
}

- (NSString *)tableView:(UITableView *)aTableView titleForHeaderInSection:(NSInteger)section
{
    // Return the title for a given section
    NSArray *titles = [self.results sectionIndexTitles];
    if (titles.count <= section) return @"Error";
    return [titles objectAtIndex:section];
}

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
    // Query the titles for the section associated with an index title
    return [self.results.sectionIndexTitles indexOfObject:title];
}

条件查询

1
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name contains[cd] %@", query];

table view edit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    // delete request
    if (editingStyle == UITableViewCellEditingStyleDelete)
    {
        NSError *error = nil;
        [self.context deleteObject:[fetchedResultsController objectAtIndexPath:indexPath]];
        if (![self.context save:&error]) NSLog(@"Error: %@", [error localizedDescription]);
    }
   
    // update buttons after delete action
    [self setBarButtonItems];
   
    // update sections
    [self performFetch];
}

让Core Data支持undo,redo

每个context有各自的undoManager
self.undoManager = self.context.undoManager;

添加记录

1
2
3
4
5
6
7
8
9
10
11
12
13
    [self.context.undoManager beginUndoGrouping];
   
    // build a new item and set its action field
    ToDoItem *item = (ToDoItem *)[NSEntityDescription insertNewObjectForEntityForName:@"ToDoItem" inManagedObjectContext:self.context];
    item.action = todoAction;
    item.sectionName = [[todoAction substringToIndex:1] uppercaseString];
   
    // save the new item
    NSError *error;
    if (![self.context save:&error]) NSLog(@"Error: %@", [error localizedDescription]);
   
    [self.context.undoManager endUndoGrouping];
    [self.context.undoManager setActionName:@"Add"];

删除记录

1
2
3
4
5
6
7
8
9
10
11
12
    [self.context.undoManager beginUndoGrouping];

    // delete request
    if (editingStyle == UITableViewCellEditingStyleDelete)
    {
        NSError *error = nil;
        [self.context deleteObject:[fetchedResultsController objectAtIndexPath:indexPath]];
        if (![self.context save:&error]) NSLog(@"Error: %@", [error localizedDescription]);
    }

    [self.context.undoManager endUndoGrouping];
    [self.context.undoManager setActionName:@"Delete"];

注:ios undo/redo的指令默认是靠晃动设备完成的。ios模拟器上shake的快捷键是control+command+z

如果要禁止晃动产生undo

1
application.applicationSupportsShakeToEdit = NO;

更多undo信息,请阅读Using Undo on iPhone
相关源码:
C19-CoreData(来自The.iPhone.Developers.Cookbook,2nd)
CoreDataBooks

Xcode and #pragma mark

星期三, 五月 25th, 2011

原文

形式

1
2
#pragma mark -
#pragma mark Initialization

#pragma mark在xcode中的作用很简单,使你在使用xcode的时候function list会分组把function按你的标记隔开
paragma

Core Data

星期二, 五月 24th, 2011

http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/CoreData/cdProgrammingGuide.html

路径问题
sqlite文件不能作为resource bundle存在在项目中,否则会被每次覆盖
最好存储在Library或Documents

获得Documents路径

1
2
3
4
5
6
- (NSString *)applicationDocumentsDirectory {
   
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
    return basePath;
}

检查documents路径下是否有db文件,如果没有,从resource bundle路径下copy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    NSString * dbPath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:@"user.db"];
    NSLog(@"dbPath=%@", dbPath);
   
    NSFileManager * fm = [NSFileManager defaultManager];
   
    if(![fm fileExistsAtPath:dbPath])
    {
        NSString * localPath = [[NSBundle mainBundle] pathForResource:@"user" ofType:@"db"];
        if(localPath)
            [fm copyItemAtPath:localPath toPath:dbPath error:nil];
    }
   
    self.window.rootViewController = self.viewController;
    self.viewController.dbPath = dbPath;
    [self.window makeKeyAndVisible];
    return YES;
}

MacOS 启用root用户

星期一, 五月 23rd, 2011

从 Apple 菜单中,选取系统偏好设置…。
从显示菜单中,选取帐户。
点按锁图标并使用管理员帐户进行鉴定。
点按“登录选项…”。
点按右下部的“编辑…”或“加入…”按钮。
点按“打开目录实用工具…”按钮。
点按“目录实用工具”窗口中的锁图标。
输入管理员帐户名称和密码,然后点按“好”。
从编辑菜单中选取启用 Root 用户。
在“密码”和“验证”字段中输入您想要使用的 root 密码,然后点按“好”。

Cocoa notes (17) Copy, Cut, Paste, Menu

星期一, 五月 23rd, 2011

要处理copy-paste必须使用UIResponderStandardEditActions protocol,建议UIResponderStandardEditActions的实现类为UIResponder

UIResponderStandardEditActions有以下声明方法
Handling Copy, Cut, Delete, and Paste Commands
– copy:
– cut:
– delete:
– paste:
Handling Selection Commands
– select:
– selectAll:

二进制数据必须存储于 NSData中,如果是其他类型的数据,必须存储于 NSString, NSArray, NSDictionary, NSDate, NSNumber, or NSURL等类型中。

To write a data object, send a setData:forPasteboardType: message to the pasteboard object.
To write a property-list object, send a setValue:forPasteboardType: message to the pasteboard object.

copy或cut流程
1. 获取paste board对象
2. 获得目前选中的对象
3. 序列化成NSData或property-list
4. 使用适合的UTI保存到paste board

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)copy:(id)sender {
    UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
    ColorTile *theTile = [self colorTileForOrigin:currentSelection];
    if (theTile) {
        NSData *tileData = [NSKeyedArchiver archivedDataWithRootObject:theTile];
        if (tileData)
            [gpBoard setData:tileData forPasteboardType:ColorTileUTI];
    }
}
 
- (void)cut:(id)sender {
    [self copy:sender];
    ColorTile *theTile = [self colorTileForOrigin:currentSelection];
 
    if (theTile) {
        CGPoint tilePoint = theTile.tileOrigin;
        [tiles removeObject:theTile];
        CGRect tileRect = [self rectFromOrigin:tilePoint inset:TILE_INSET];
        [self setNeedsDisplayInRect:tileRect];
     }
}

paste的流程
1. 获取paste board对象
2. 用 containsPasteboardTypes: 或 pasteboardTypes 检查paste board里含有你的程序所能使用的对象类型
注意你应该在之前的canPerformAction:withSender:方法中就检验过此步骤(如果数据类型不对则paste menuitem是disable状态).
3. 如果paste board中的对象是你的程序可以处理的对象,则使用一下方法读取
*dataForPasteboardType: if the data to be read is encapsulated in an NSData object.
*valueForPasteboardType: if the data to be read is encapsulated in a property-list object (see “Copying and Cutting the Selection”).
4. 把该对象从paste board还原
5. 对象显示到程序里的相应位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- (void)paste:(id)sender {

    UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];

    NSArray *pbType = [NSArray arrayWithObject:ColorTileUTI];

    ColorTile *theTile = [self colorTileForOrigin:currentSelection];

    if (theTile == nil && [gpBoard containsPasteboardTypes:pbType]) {

        NSData *tileData = [gpBoard dataForPasteboardType:ColorTileUTI];

        ColorTile *theTile = (ColorTile *)[NSKeyedUnarchiver unarchiveObjectWithData:tileData];

        if (theTile) {

            theTile.tileOrigin = self.currentSelection;

            [tiles addObject:theTile];

            CGRect tileRect = [self rectFromOrigin:currentSelection inset:TILE_INSET];

            [self setNeedsDisplayInRect:tileRect];

        }

    }

}

显示Menu的步骤
1. 使用UIMenuController 的sharedMenuController 方法获得Menu对象(singleton)
2. 计算选中区域的边界,然后用把这个矩形区域作为参数传递给setTargetRect:inView:
3. 用setMenuVisible:animated: 显示菜单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *theTouch = [touches anyObject];
 
    if ([theTouch tapCount] == 2  && [self becomeFirstResponder]) {
 
        // selection management code goes here...
 
        // bring up edit menu.
        UIMenuController *theMenu = [UIMenuController sharedMenuController];
        CGRect selectionRect = CGRectMake (currentSelection.x, currentSelection.y, SIDE, SIDE);
        [theMenu setTargetRect:selectionRect inView:self];
        [theMenu setMenuVisible:YES animated:YES];
 
    }
}

becomeFirstResponder是为了确认当前view是firstResponder

在显示菜单前,会调用代理方法:canPerformAction:withSender: ,在这里可以设置菜单的某些选项可用或不可用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    BOOL retValue = NO;
    ColorTile *theTile = [self colorTileForOrigin:currentSelection];
 
    if (action == @selector(paste:) )
        retValue = (theTile == nil) &&
             [[UIPasteboard generalPasteboard] containsPasteboardTypes:
             [NSArray arrayWithObject:ColorTileUTI]];
    else if ( action == @selector(cut:) || action == @selector(copy:) )
        retValue = (theTile != nil);
    else
        retValue = [super canPerformAction:action withSender:sender];
    return retValue;
}

设置自定义菜单项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *theTouch = [touches anyObject];
    if ([theTouch tapCount] == 2) {
        [self becomeFirstResponder];
        UIMenuItem *menuItem = [[UIMenuItem alloc] initWithTitle:@"Change Color" action:@selector(changeColor:)];
        UIMenuController *menuCont = [UIMenuController sharedMenuController];
        [menuCont setTargetRect:self.frame inView:self.superview];
        menuCont.arrowDirection = UIMenuControllerArrowLeft;
        menuCont.menuItems = [NSArray arrayWithObject:menuItem];
        [menuCont setMenuVisible:YES animated:YES];
    }
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {}
 
- (BOOL)canBecomeFirstResponder { return YES; }
 
- (void)changeColor:(id)sender {
    if ([self.viewColor isEqual:[UIColor blackColor]]) {
        self.viewColor = [UIColor redColor];
    } else {
        self.viewColor = [UIColor blackColor];
    }
    [self setNeedsDisplay];
}

在菜单项被选中时,菜单会自动隐藏,如果你不想让它自动隐藏

1
[UIMenuController setMenuController].menuVisible = YES;

源码
CopyPasteTile

相关阅读
Uniform Type Identifiers Overview

Cocoa notes (16) KeyBoard

星期六, 五月 21st, 2011

KeyBoard Notifications(通过系统通知获得键盘的位置大小等)
UIKeyboardWillShowNotification
UIKeyboardDidShowNotification
UIKeyboardWillHideNotification
UIKeyboardDidHideNotification

可以通过UIKeyboardFrameBeginUserInfoKey 和 UIKeyboardFrameEndUserInfoKey,从userInfo的字典数据结构中获得键盘size和坐标的信息
应当每次处理这些信息,因为键盘所占用屏幕空间并不每次都一样(根据键盘的类型和定义)

显示键盘
一般来说,当用户选择可输入文本的视图时会自动弹出系统键盘,也可以通过编程方式,调出键盘(becomeFirstResponder相当于获得焦点,resignFirstResponder相当于失去焦点)

1
[textfield becomeFirstResponder];

隐藏键盘
和显示键盘不同,键盘不会自动隐藏,而需要开发者自行关闭

1
[myTextField resignFirstResponder];

当键盘弹出时挡住要输入内容位置时的处理
1. 获得键盘的位置和Size
2. 计算需要调整的高度
3. 将UIScrollView滚动到相应位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 在你的ViewController 某处调用这个方法注册notification方法

- (void)registerForKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] addObserver:self
            selector:@selector(keyboardWasShown:)
            name:UIKeyboardDidShowNotification object:nil];
   [[NSNotificationCenter defaultCenter] addObserver:self
             selector:@selector(keyboardWillBeHidden:)
             name:UIKeyboardWillHideNotification object:nil];
}

// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWasShown:(NSNotification*)aNotification
{
    NSDictionary* info = [aNotification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
 
    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);

    scrollView.contentInset = contentInsets;

    scrollView.scrollIndicatorInsets = contentInsets;
    // If active text field is hidden by keyboard, scroll it so it's visible
    // Your application might not need or want this behavior.
    CGRect aRect = self.view.frame;
    aRect.size.height -= kbSize.height;
    if (!CGRectContainsPoint(aRect, activeField.frame.origin) ) {
        CGPoint scrollPoint = CGPointMake(0.0, activeField.frame.origin.y-kbSize.height);
        [scrollView setContentOffset:scrollPoint animated:YES];
    }

}

 

// Called when the UIKeyboardWillHideNotification is sent

- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
    UIEdgeInsets contentInsets = UIEdgeInsetsZero;
    scrollView.contentInset = contentInsets;
    scrollView.scrollIndicatorInsets = contentInsets;
}

相关源码
KeyboardAccessory

A very good sample demostrate iOS UIKit Components

星期六, 五月 21st, 2011

UICatalog

About Text, Web, and Editing Support in iOS

星期六, 五月 21st, 2011

https://developer.apple.com/library/ios/#documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/Introduction/Introduction.html

URL Loading System Programming Guide

星期五, 五月 20th, 2011

http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html#//apple_ref/doc/uid/10000165-BCICJDHA

HelloPloy 作业

星期三, 五月 18th, 2011

HelloPoly2