【转】iOS中的一些宏定义,用于进行log输出定位Bug。

转自:http://www.linuxidc.com/Linux/2012-12/76754.htm

我该如何在日志输出信息中添加上下文信息,例如当前方法或者行号。

C预处理器提供了一些标准宏,可以提供当前文件,行号,或者函数的信息。另外,Objective-C有_cmd隐式参数,可以提供当前函数的选择器,以及将选择器和类转换为字符串的功能。你可以在调试或者错误处理时在NSLog语句中提供这些上下文信息。

下面是打印当前方法和行号的例子。

NSMutableArray *someObject = [NSMutableArray array];
NSLog(@"%s:%d someObject=%@", __func__, __LINE__, someObject);
[someObject addObject:@"foo"];
NSLog(@"%s:%d someObject=%@", __func__, __LINE__, someObject);

下面是在日志语句中很有用的非常常见的宏和表达式。

C/C++/Objective-C中用于日志输出的预处理宏.

Macro Format Specifier Description
__func__ %s 当前函数前面
__LINE__ %d 源码文件中的行号
__FILE__ %s 源码文件完整路径
__PRETTY_FUNCTION__ %s 和__func__类似, 但是在 C++ 代码中包含更多的信息.

Objective-C中用于日志输出的表达式

Expression Format Specifier Description
NSStringFromSelector(_cmd) %@ 当前选择器的名字
NSStringFromClass([self class]) %@ 当前对象类的名字
[[NSString stringWithUTF8String:__FILE__] lastPathComponent] %@ 源码文件的名称
[NSThread callStackSymbols] %@  

当前栈信息的刻度字符串数组。仅用于调试,不用向终端用户展示或者在代码中用作任何逻辑。

 

KVO in iOS Development

继续挖坑

这篇文章还不错:Objective-C KVO 编程 改善现有iOS代码设计
———– 下面是这篇文章的内容,仅作为backup ———-

KVC很多人都知道,那么什么是KVO呢?Key Value Observing,直译为:基于键值的观察者。
KVO的优点
当有属性改变,KVO会提供自动的消息通知。这样的架构有很多好处。首先,开发人员不需要自己去实现这样的方案:每次属性改变了就发送消息通知。
这 是KVO机制提供的最大的优点。因为这个方案已经被明确定义,获得框架级支持,可以方便地采用。开发人员不需要添加任何代码,不需要设计自己的观察者模 型,直接可以在工程里使用。其次,KVO的架构非常的强大,可以很容易的支持多个观察者观察同一个属性,以及相关的值。

主要用于有关视图界面交互编程中,比如,实体(或者叫名词、或者叫域模型),在应用中表示名词的部分,类似Java中的Java Bean。再具体点儿,在下文的示例中。
图书(Book类),就是个实体。它的属性有书名(name)和价格(price)。那么,在界面开发中,可能有多个视图和这个实体有关联。
如果等实体(Book)的价格(price)发生了变化,这些关联的界面都要被修改。

比较好的做法是使用观察者模式,各个界面都注册观察者,观察图书的价格变化,当变化后改动自己的视图。

ObjC中提供了这个模式的解决方案,就是KVO。以下用简单示例说明KVO的实现方式。

Book类,头文件:

#import 

@interface Book : NSObject { 
    NSString *name; 
    float price; 
}

@end

 

Book类的实现文件,没做任何事情,不贴了。

现在,假设我有个视图,MyView,我这里为了不带入实际视图类的复杂性,只是模拟一个。用普通类。头文件:

#import 

@class Book;

@interface MyView : NSObject { 
    Book *book; 
}

- (id) init:(Book *)theBook;

@end

 

实现文件:

#import "MyView.h"

@implementation MyView

- (id) init:(Book *)theBook { 
    if(self=[super init]){ 
        book=theBook; 
        [book addObserver:self forKeyPath:@"price" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil]; 
    } 
    return self; 
}

- (void) dealloc{ 
    [book removeObserver:self forKeyPath:@"price"]; 
    [super dealloc]; 
}

- (void)observeValueForKeyPath:(NSString *)keyPath 
                      ofObject:(id)object 
                        change:(NSDictionary *)change 
                       context:(void *)context{ 
    if([keyPath isEqual:@"price"]){ 
        NSLog(@">>>>>>>price is changed"); 
        NSLog(@"old price is %@",[change objectForKey:@"old"]); 
        NSLog(@"new price is %@",[change objectForKey:@"new"]);
    } 
}

@end

 

这里的init方法中,可以看到向book实例增加了观察者,是针对价格price属性的。这里用的:

options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew

可以让通知携带旧的price值和新的price值。后面会看到。observeValueForKeyPath方法,就是当price属性发生变化后,调用的方法。

main方法中调用的代码:

Book *book4=[[Book alloc] init]; 
NSArray *bookProperties=[NSArray arrayWithObjects:@"name",@"price",nil]; 
NSDictionary *bookPropertiesDictionary=[book4 dictionaryWithValuesForKeys:bookProperties]; 
NSLog(@"book values: %@",bookPropertiesDictionary);

[[[MyView alloc] init:book4] autorelease];

NSDictionary *newBookPropertiesDictionary=[NSDictionary dictionaryWithObjectsAndKeys:@"《Objective C入门》",@"name", 
                                           @"20.5",@"price",nil]; 
[book4 setValuesForKeysWithDictionary:newBookPropertiesDictionary]; 
NSLog(@"book with new values: %@",[book4 dictionaryWithValuesForKeys:bookProperties]);

 

在这里引发了price属性变化,触发了MyView的处理。

另外,要注意,在Book实例释放前,要删除观察者,否则会报错,这里是在MyView里面实现的:

- (void) dealloc{ 
    [book removeObserver:self forKeyPath:@"price"]; 
    [super dealloc]; 
}

 

这里假定MyView实例的生命周期小于等于Book实例。实际使用可能要根据情况在合适的地方addObserver和removeObserver。


KVC in iOS Development

原来objective-c里面有这么强大的东西,以前只是用过,没有深入了解。
转载一个,挖个坑,以后慢慢填。

 

1.    基本概念

MODEL

主要是英文文档里面经常出现的一些概念,讲解一下,方便英文文档的阅读。

IOS应用开发是遵循MVC设计模式的,Cocoa框架用Object Modeling的规则来规范一个Model的实现。

ObjectModeling有如下几个概念的规定:

Entity:表示持有数据的一个实体

Property实体中的成员,分为Attribute和:Relationship

Attribute:基本类型的成员,比如:数字、NSString。

Relationship:指向其它Entity的关系型成员,它又有to 1Relationship和to manyRelationship的区别。

AccessorMethod:getter,setter。

举例:

如下是一个部门和员工关系的Model

部门:Department

部门名称(NSString) 成员(NSArray) 部长(Employee)
MIC (所有成员) 老王(一个成员)
MIB    

员工:Employee

名字(NSStirng) 所属部门(Department)
小王 MIC
   

 

使用KVC、KVO的优势

通过规定了一组通用的Cocoa命名法则、调用规则等,实现了如下功能:

²  使用一对高度规范化的访问方法,获取以及设置任何对象的任何属性的值。

²  通过继承一个特定的方法,并且指定希望监视的对象及希望监视的属性名称,就能在该对象的指定属性的值发生改变时,得到一个“通知”(尽管这不是一个真正意 义上的通知),并且得到相关属性的值的变化(原先的值和改变后的新值)。

²  通过一个简单的函数调用,使一个视图对象的一个指定属性随时随地都和一个控制器对象或模型对象的一个指定属性保持同步。

2.    KVC

2.1  概述

KVC是KeyValue Coding的简称,它是一种可以直接通过字符串的名字(key)来访问类属性的机制。而不是通过调用Setter、Getter方法访问。

当使用KVO、Core Data、CocoaBindings、AppleScript(Mac支持)时,KVC是关键技术。

 

2.2  如何使用KVC

关键方法定义在:NSKeyValueCodingprotocol

KVC支持类对象和内建基本数据类型。

 

2.2.1         获取值

valueForKey:,传入NSString属性的名字。

valueForKeyPath:,传入NSString属性的路径,xx.xx形式。

valueForUndefinedKey它的默认实现是抛出异常,可以重写这个函数做错误处理。

2.2.2         修改值

setValue:forKey:

setValue:forKeyPath:

setValue:forUndefinedKey:

setNilValueForKey: 当对非类对象属性设置nil时,调用,默认抛出异常。

2.2.3         一对多关系成员的情况

mutableArrayValueForKey:有序一对多关系成员  NSArray

mutableSetValueForKey:无序一对多关系成员  NSSet

 

示例:

 

2.3  KVC的实现细节

搜索Setter、Getter方法

这一部分比较重要,能让你了解到KVC调用之后,到底是怎样获取和设置类成员值的。

2.3.1         搜索简单的成员

如:基本类型成员,单个对象类型成员:NSInteger,NSString*成员。

a. setValue:forKey的搜索方式:

  1. 首先搜索set<Key>:方法

如果成员用@property,@synthsize处理,因为@synthsize告诉编译器自动生成set<Key>:格式的setter方法,所以这种情况下会直接搜索到。

注意:这里的<Key>是指成员名,而且首字母大写。下同。

  1. 上面的setter方法没有找到,如果类方法accessInstanceVariablesDirectly返回YES(注:这是NSKeyValueCodingCatogery中实现的类方法,默认实现为返回YES)。

那么按_<key>,_is<Key>,<key>,is<key>的顺序搜索成员名。

  1. 如果找到设置成员的值,如果没有调用setValue:forUndefinedKey:。

 

b. valueForKey:的搜索方式:

  1. 首先按get<Key>、<key>、is<Key>的顺序查找getter方法,找到直接调用。如果是bool、int等内建值类型,会做NSNumber的转换。

  2. 上面的getter没有找到,查找countOf<Key>、objectIn<Key>AtIndex:、<Key>AtIndexes格式的方法。

如果countOf<Key>和另外两个方法中的一个找到,那么就会返回一个可以响应NSArray所有方法的代理集合(collection proxy object)。发送给这个代理集合(collection proxy object)的NSArray消息方法,就会以countOf<Key>、objectIn<Key>AtIndex:、<Key>AtIndexes这几个方法组合的形式调用。还有一个可选的get<Key>:range:方法。

  1. 还没查到,那么查找countOf<Key>、enumeratorOf<Key>、memberOf<Key>:格式的方法。

如果这三个方法都找到,那么就返回一个可以响应NSSet所有方法的代理集合(collection proxy object)。发送给这个代理集合(collection proxy object)的NSSet消息方法,就会以countOf<Key>、enumeratorOf<Key>、memberOf<Key>:组合的形式调用。

  1. 还是没查到,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按_<key>,_is<Key>,<key>,is<key>的顺序直接搜索成员名。

  2. 再没查到,调用valueForUndefinedKey:。

 

2.3.2         查找有序集合成员,比如NSMutableArray

mutableArrayValueForKey:搜索方式如下:

  1. 搜索insertObject:in<Key>AtIndex:、removeObjectFrom<Key>AtIndex:或者insert<Key>:atIndexes、remove<Key>AtIndexes:格式的方法。

如果至少一个insert方法和至少一个remove方法找到,那么同样返回一个可以响应NSMutableArray所有方法的代理集合。那么发送给这个代理集合的NSMutableArray消息方法,以insertObject:in<Key>AtIndex:、removeObjectFrom<Key>AtIndex:、insert<Key>:atIndexes、remove<Key>AtIndexes:组合的形式调用。还有两个可选实现的接口:replaceObjectIn<Key>AtIndex:withObject:、replace<Key>AtIndexes:with<Key>:。

  1. 否则,搜索set<Key>:格式的方法,如果找到,那么发送给代理集合的NSMutableArray最终都会调用set<Key>:方法。

也就是说,mutableArrayValueForKey取出的代理集合修改后,用set<Key>:重新赋值回去。这样做效率会差很多,所以推荐实现上面的方法。

  1. 否则,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按_<key>,<key>的顺序直接搜索成员名。如果找到,那么发送的NSMutableArray消息方法直接转交给这个成员处理。

  2. 再找不到,调用setValue:forUndefinedKey:。

 

2.3.3         搜索无序集合成员,如:NSSet。

mutableSetValueForKey:搜索方式如下:

  1. 搜索add<Key>Object:、remove<Key>Object:或者add<Key>:、remove<Key>:格式的方法,如果至少一个insert方法和至少一个remove方法找到,那么返回一个可以响应NSMutableSet所有方法的代理集合。那么发送给这个代理集合的NSMutableSet消息方法,以add<Key>Object:、remove<Key>Object:、add<Key>:、remove<Key>:组合的形式调用。还有两个可选实现的接口:intersect<Key>、set<Key>:。

  2. 如果reciever是ManagedObejct,那么就不会继续搜索了。

  3. 否则,搜索set<Key>:格式的方法,如果找到,那么发送给代理集合的NSMutableSet最终都会调用set<Key>:方法。也就是说,mutableSetValueForKey取出的代理集合修改后,用set<Key>:重新赋值回去。这样做效率会差很多,所以推荐实现上面的方法。

  4. 否则,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按_<key>,<key>的顺序直接搜索成员名。如果找到,那么发送的NSMutableSet消息方法直接转交给这个成员处理。

  5. 再找不到,调用setValue:forUndefinedKey:。

 

KVC还提供了下面的功能

2.4  值的正确性核查

KVC提供属性值确认的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。

实现核查方法

为如下格式:validate<Key>:error:

如:

 

  1. -(BOOL)validateName:(id *)ioValue error:(NSError **)outError
  2. {
  3.     // The name must not be nil, and must be at least two characters long.
  4.     if ((*ioValue == nil) || ([(NSString *)*ioValue length] < 2]) {
  5.         if (outError != NULL) {
  6.             NSString *errorString = NSLocalizedStringFromTable(
  7.                     @”A Person’s name must be at least two characters long”, @”Person”,
  8.                     @”validation: too short name error”);
  9.             NSDictionary *userInfoDict =
  10.                 [NSDictionary dictionaryWithObject:errorString
  11.                                             forKey:NSLocalizedDescriptionKey];
  12.             *outError = [[[NSError alloc] initWithDomain:PERSON_ERROR_DOMAIN
  13.                                                     code:PERSON_INVALID_NAME_CODE
  14.                                                 userInfo:userInfoDict] autorelease];
  15.         }
  16.         return NO;
  17.     }
  18.     return YES;
  19. }

 

调用核查方法:

validateValue:forKey:error:,默认实现会搜索 validate<Key>:error:格式的核查方法,找到则调用,未找到默认返回YES。

注意其中的内存管理问题。

 

2.5  集合操作

集合操作通过对valueForKeyPath:传递参数来使用,一定要用在集合(如:array)上,否则产生运行时刻错误。其格式如下:

 

Left keypath部分:需要操作对象路径。

Collectionoperator部分:通过@符号确定使用的集合操作。

Rightkey path部分:需要进行集合操作的属性。

2.5.1         数据操作

@avg:平均值

@count:总数

@max:最大

@min:最小

@sum:总数

确保操作的属性为数字类型,否则运行时刻错误。

2.5.2         对象操作

针对数组的情况

@distinctUnionOfObjects:返回指定属性去重后的值的数组

@unionOfObjects:返回指定属性的值的数组,不去重

属性的值不能为空,否则产生异常。

2.5.3         数组操作

针对数组的数组情况

@distinctUnionOfArrays:返回指定属性去重后的值的数组

@unionOfArrays:返回指定属性的值的数组,不去重

@distinctUnionOfSets:同上,只是返回值为NSSet

 

示例代码:

 

2.6  效率问题

相比直接访问KVC的效率会稍低一点,所以只有当你非常需要它提供的可扩展性时才使用它。

解决iOS开发中调用UIScrollView或UITableView的setContentOffset方法产生的抖动

现在“下拉刷新”和“上拉加载更多”都是常用的移动端程序的设计了。

实现的话,下拉刷新有现成的开源库:EGOTableViewPullRefresh

上拉加载更多的话,自己研究,写了一个类似的tableViewCell。但是在ScrollViewDidEndDragging方法中修改scrollView的setContentInset时发生了抖动。

花了挺长时间研究,最后发现原因在于当调用setContentInset的时候scrollView的contentOffset会跟着一起变化(由于是设置inset的bottom,所以scrollview会自动到最底端),然后由于是在ScrollViewDidEndDragging,视图还要从以前的位置动画回到scrollview的末尾(bounce效果)。于是修改inset之后造成的当前contentOffset位置就和bounce动画的初值不等,造成了抖动。

解决办法也很简单,就是在setContentInset之后,立马重设一下contentOffset。

        float contentTop = scrollView.contentOffset.y;
        [scrollView setContentInset:oriIns];
        [scrollView setContentOffset:CGPointMake(0, contentTop) animated:NO];

iOS开发中利用MFMessageComposeViewController发送短信

从SDK4.0开始,就可以在程序内使用MFMessageComposeViewController来发送短信了(如果设备支持的话)。

废话少说,代码如下:

self.msgCtrl = [[MFMessageComposeViewController alloc] init];
            [self.msgCtrl release];
            if (![MFMessageComposeViewController canSendText]) {
                UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"设备不支持短信发送" message:nil delegate:nil cancelButtonTitle:@"取消" otherButtonTitles:nil];
                [av show];
                [av release];
                return;
            }
            [self.msgCtrl setBody:@"短信内容"];
            [self.msgCtrl setRecipients:[self.selectedContactPhoneDict allKeys]];
!!!:            self.msgCtrl.messageComposeDelegate = self;
            [self presentModalViewController:self.msgCtrl animated:YES];

特别是标注了!!!的这一行,坑爹的delegate名字是messageComposeDelegate,这样才会触发下面的回调:

-(void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result{
    switch (result) {
        case MessageComposeResultCancelled:
            [[TKAlertCenter defaultCenter] postAlertWithMessage:@"已取消短信发送"];
            break;
        case MessageComposeResultSent:
            [[TKAlertCenter defaultCenter] postAlertWithMessage:@"成功发送短信"];
            break;
        case MessageComposeResultFailed:
            [[TKAlertCenter defaultCenter] postAlertWithMessage:@"短信发送失败"];
            break;
        default:
            break;
    }
    [self dismissModalViewControllerAnimated:YES];
}

利用XCode中Interface Builder的Runtime Attribute来设置运行时参数,比如圆角

要设置一个view的圆角,可以通过在代码里面写上:

aView.layer.cornerRadius = 10

这样的内容来将其圆角设为10px。



其实在interface builder中,完全可以设置圆角,不用写一行代码:

选择要设置圆角的view之后,在这个页面下可以设置其runtime attribute,只需要添加一个keypath,设置好它的类型和值就可以了。
这里例子中,keypath是“layer.cornerRadius”,类型是“Number”,值是“10”。
不过要记得将view的clipSubviews设为true哦。

旧项目在iOS6中链接不过的问题,提示缺armv7s

升级了xcode之后,支持iOS6和iPhone5,不过Build项目的时候,出现错误提示信息:

ld: file is universal (3 slices) but does not contain a(n) armv7s slice

实际上是引用的第三方库导致了这个链接错误。

解决办法有三个,随便哪种都能解决:
1.升级涉及到的.a文件
2.在target的Build Settings里面,将Build Active Architecture Only改成YES
3.在target的Build Settings里面,找到Valid Architectures,删除其中的armv7s

所以还是觉得,如果是开源库,直接把源代码包含进项目比较靠谱。

viewWillAppear不执行的问题

今天遇到了viewWillAppear函数不执行的问题,最后找到了症结所在。
<br>
那就是使用了UINavigationControllerDelegate,但是没有在相应的函数中显示调用即将显示的viewController的viewWillAppear方法。
系统的UINavigationControllerDelegate会自动调用这4个方法,但是如果是自己重载了delegate的话,就需要手动去调用。

iOS中的文件存放法则

Apple对应用程序放在沙盒中的文件有严格要求,主要有:

    存放位置要求

  1. 用户创建的文件,(程序不能自动生成的),需要放在Documents\
  2. 缓存文件,需要放在Library\Caches\
  3. 临时文件,放在tmp\,而且要注意清空
    文件备份
    这个可以通过设置文件的一个属性来控制,具体见下面代码

  1. 除了用户创建和编辑的文件,不允许保存到iTunes和iCloud
  2. 用户升级程序之后,所有Documents\和Library\的文件会自动复制到新的bundle中去



下面的代码是如何设置属性,让apple在备份的时候,不会包含这个文件。
对于iOS版本5.1之前和之后的处理方式是不一样的。

+(BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
    if (![[NSFileManager defaultManager] fileExistsAtPath: [URL path]]) {
        return NO;
    }
    if ([[UIDevice currentDevice].systemVersion floatValue] >= 5.1) {
        NSError *error = nil;
        BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
                                      forKey: NSURLIsExcludedFromBackupKey error: &error];
        if(!success){
            NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
        }
        return success;
    } else {
        const char* filePath = [[URL path] fileSystemRepresentation];
        const char* attrName = "com.apple.MobileBackup";
        u_int8_t attrValue = 1;
        int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
        return result == 0;
    }
}

UITableView的背景颜色以及层次研究

UITableView的背景颜色,并不是UITableView的backgroundColor。
其内部实际上创建了一个UIView来专门呈现设定的背景颜色。
感觉它的subview层次从下往上是这样的:

  1. 这个呈现backgroundColor的view
  2. 放置其中的cell们
  3. scroll indicators

而且每次添加一个cell,tableView会自动将这个cell放置在backgroundView的上面一层,而且UITableView的sendViewToBack和bringToFront函数应该是重新定义过的,并不会按照普通UIView的方式去执行。

iOS开发中使用平铺图像作为UIScrollView或者UITableView的可滚动背景

UIScroView和TableView都可以设置一张图片作为背景,或者设置一个颜色。
但是这两种方法设定的颜色都是固定的,不会随着表格或者Scroll的滚动而滚动。
不过,要使用一张图片以平铺的形式贴在可滚动区域上也是可以的,只需要一个语句即可:

self.tableView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"loginBkgWithoutLogo"]];

UIImagePickerController返回的图片可能是旋转的需要用imageOrientation将其矫正

UIImagePickerController返回的照片带有方向信息,如果直接上传到服务器的话,可能造成旋转了90°(当手机竖直拍照)的情况。而且如果直接取其jpeg数据,或者将UIImage保存到本地的话,就会丢失这个方向信息,导致下一次读取出来图片就是颠倒的。


为了让上传到服务器或者保存的本地的图片和照相时候一样,需要利用UIImage的imageOrientation将其矫正。


已经有一个开源的Category来处理这个问题,原理就是根据UIImage的imageOrientation属性反过来绘制到新的CGImage里面,然后保存为正常的UIImage。
链接是:https://gist.github.com/1531596

通过NSCalendar与NSDate的年月日时分秒等元素进行交互

在iOS中,表示时间的类是NSDate,但是NSDate仅仅保存了一个时刻(据1970年1月1日凌辰开始的秒数)。
想获取这个时刻的年,月,日,时,分,秒等等信息,光从NSDate是不行的。
而获取这些元素的途径是通过NSCalendar类来进行的。


下面的代码展示了如何从一个NSDate获取这些元素,并通过指定的元素来创建NSDate对象

//首先创建NSCalendar的实例,可以简单的用当前实例,也可以创建其它的历法对应的实例。
NSCalendar *cal = [NSCalendar currentCalendar];

//下面通过NSCalendar来获取各个元素,保存在类NSDateComponents的实例中
//需要通过函数参数components指定希望获取的元素,详细的枚举后面会列出
NSDateComponents *dateComps = [cal components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit|NSHourCalendarUnit|NSMinuteCalendarUnit fromDate:startDate];
int year = [dateComps year];
int month = [dateComps month];
int day = [dateComps day];
int hour = [dateComps hour];
int minute = [dateComps minute];
int second = [dateComps second];

//也可以通过NSCanlendar和NSDateComponents来创建NSDate
NSDate *newDate = [cal dateFromComponents:dateComps];
 <br \>

可用的元素枚举定义如下

enum {
    NSEraCalendarUnit = kCFCalendarUnitEra,
    NSYearCalendarUnit = kCFCalendarUnitYear,
    NSMonthCalendarUnit = kCFCalendarUnitMonth,
    NSDayCalendarUnit = kCFCalendarUnitDay,
    NSHourCalendarUnit = kCFCalendarUnitHour,
    NSMinuteCalendarUnit = kCFCalendarUnitMinute,
    NSSecondCalendarUnit = kCFCalendarUnitSecond,
    NSWeekCalendarUnit = kCFCalendarUnitWeek /* NS_DEPRECATED(10_4, 10_7, 2_0, 5_0) */,
    NSWeekdayCalendarUnit = kCFCalendarUnitWeekday,
    NSWeekdayOrdinalCalendarUnit = kCFCalendarUnitWeekdayOrdinal,
#if MAC_OS_X_VERSION_10_6 <= MAC_OS_X_VERSION_MAX_ALLOWED || __IPHONE_4_0 <= __IPHONE_OS_VERSION_MAX_ALLOWED
    NSQuarterCalendarUnit = kCFCalendarUnitQuarter,
#endif
#if MAC_OS_X_VERSION_10_7 <= MAC_OS_X_VERSION_MAX_ALLOWED || __IPHONE_5_0 <= __IPHONE_OS_VERSION_MAX_ALLOWED
    NSWeekOfMonthCalendarUnit = kCFCalendarUnitWeekOfMonth,
    NSWeekOfYearCalendarUnit = kCFCalendarUnitWeekOfYear,
    NSYearForWeekOfYearCalendarUnit = kCFCalendarUnitYearForWeekOfYear,
#endif
#if MAC_OS_X_VERSION_10_7 <= MAC_OS_X_VERSION_MAX_ALLOWED || __IPHONE_4_0 <= __IPHONE_OS_VERSION_MAX_ALLOWED
        NSCalendarCalendarUnit = (1 << 20),
        NSTimeZoneCalendarUnit = (1 << 21),
#endif
};
typedef NSUInteger NSCalendarUnit;

iOS开发中混合使用ARC和非ARC项目

SDK5.0引入了ARC,到现在已经一年了,开始发现有很多项目会混合使用这两个方案。比如:

1.自己的旧项目没有使用ARC,但是引入的第三方库却是使用了ARC的。
2.自己的新项目使用了ARC,但是引入的第三方库或者以前写的代码却没有使用ARC。

这两种情况下,直接肯定是通不过编译的。可以通过升级旧项目,让其使用ARC来解决,但这个办法有时候会很麻烦。
有一个简单的办法就是,可以指定单个文件是否采用ARC来进行编译。
方法就是在Build Phase里面的Compile Source里面找到需要特殊处理的文件,加上编译选项(Compiler Flags),具体针对上面两种情况有所区别。

1.对于第一个情况,给采用了ARC的源文件,添加-fobjc-arc选项
2.对于第二种情况,添加-fno-objc-arc

此外,xcode貌似有点问题,在点击某个源文件的Compiler Flags条目的时候,应该显示光标的地方却什么也没有提示,输入字符也没有echo,只有敲完之后,选择其它文件才能看到添加的编译选项···这真无语。。

<br>
对于新写的各种插件,可以这么干:

-(void)dealloc{
    //do something in common
#if !__has_feature(objc_arc)
    [super dealloc];
#else
    //nothing
#endif
}

让它在arc和非arc都可用。

如何解决iOS瀑布流(UIScrollView或UITableView)运行不流畅

写的一个程序中用到了瀑布流的展现方式,但是发现当图片数量太大的时候,在iPhone4上会不流畅,这点很不爽。

写代码之初是做了一些优化的,比如cell重用,异步加载,但是还是很卡。

终于后来发现了症结所在,那就是,如果滑动太快,可能同时就发出了比如10个图片请求。这些请求虽然都在后台运行,但是它们可能在同一个时间点返回UI线程。这个时候如果加载图片到UIImageView太频繁,就会造成UI卡得严重。(虽然在new iPad和iPhone4s上看不出来)

在找到这个问题的同时,也发现performSelectorAfterDelay这个方法,会堆积到UI线程空闲的时候执行。而dispatch_after或者dispatch_async都会直接插入UI线程当场执行。所以这个问题其实可以用performSelectorAfterDelay来解决,测试也是非常流畅,感觉不出一点点的卡。但会出现新的问题,那就是在滑动过程中,不会加载任何图片。知道scrollView停止的时候,图片才会出来。当然这不是理想的解决方法了。这个方法也没有解决异步过程集中到达UI线程的问题。然后采用了NSOperationQueue来解决这个问题。

问题本身和UITableView加载不流畅是一样的。

解决办法

    主要要做到一下几个方面:

  1. 除了UI部分,所有的加载操作都在后台完成。
    这一点可以通过dispatch或者performSelectorInBackground或者NSOperationQueue来实现。见:
    在iOS开发中利用GCD进行多线程编程
    iOS开发中使用NSOperationQueue进行多线程操作
  2. 避免后台加载完成多个资源之后集中到达占用UI线程的处理时间太长。
    这一点可以通过NSOperationQueue来实现,将资源到UI的展现过程放在队列中逐个执行,且在每个操作完成之后进行强制等待,可以用usleep(int microSeconds)来解决。
  3. 重用cell。
    创建cell一般是很慢的,一定要重用,甚至为了performance,可以在view创建之初就创建足够多的cell在重用队列中。

iOS中UILabel滚动字幕动画的实现

有时候会遇到UILabel中的内容超出长度,显示不完全的问题。有一种解决方法是通过动画字幕来实现,比如:

  1. 字幕向左或者右滚动
  2. 字幕来回滚动

本文以后者为例来说明吧。这里先介绍UIView的通过Block实现的Animation以及其参数控制,最后是实现滚动字幕的代码。

  1. UIView有方便的动画实现方式,SDK4.0以上,提供了三个Block的动画方式:

    + (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0);
    
    + (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0); // delay = 0.0, options = 0
    
    + (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0); // delay = 0.0, options = 0, completion = NULL
    

    其中第一个最全面,可以设置UIViewAnimationOptions来控制动画的参数,比如重复,自动reverse之类的。

  2. UIViewAnimationOptions具体定义如下:

    enum {
        UIViewAnimationOptionLayoutSubviews            = 1 <<  0,
        UIViewAnimationOptionAllowUserInteraction      = 1 <<  1, // turn on user interaction while animating
        UIViewAnimationOptionBeginFromCurrentState     = 1 <<  2, // start all views from current value, not initial value
        UIViewAnimationOptionRepeat                    = 1 <<  3, // repeat animation indefinitely
        UIViewAnimationOptionAutoreverse               = 1 <<  4, // if repeat, run animation back and forth
        UIViewAnimationOptionOverrideInheritedDuration = 1 <<  5, // ignore nested duration
        UIViewAnimationOptionOverrideInheritedCurve    = 1 <<  6, // ignore nested curve
        UIViewAnimationOptionAllowAnimatedContent      = 1 <<  7, // animate contents (applies to transitions only)
        UIViewAnimationOptionShowHideTransitionViews   = 1 <<  8, // flip to/from hidden state instead of adding/removing
        
        UIViewAnimationOptionCurveEaseInOut            = 0 << 16, // default
        UIViewAnimationOptionCurveEaseIn               = 1 << 16,
        UIViewAnimationOptionCurveEaseOut              = 2 << 16,
        UIViewAnimationOptionCurveLinear               = 3 << 16,
        
        UIViewAnimationOptionTransitionNone            = 0 << 20, // default
        UIViewAnimationOptionTransitionFlipFromLeft    = 1 << 20,
        UIViewAnimationOptionTransitionFlipFromRight   = 2 << 20,
        UIViewAnimationOptionTransitionCurlUp          = 3 << 20,
        UIViewAnimationOptionTransitionCurlDown        = 4 << 20,
        UIViewAnimationOptionTransitionCrossDissolve   = 5 << 20,
        UIViewAnimationOptionTransitionFlipFromTop     = 6 << 20,
        UIViewAnimationOptionTransitionFlipFromBottom  = 7 << 20,
    };
    typedef NSUInteger UIViewAnimationOptions;
    
  3. 本文实现方法就是使用Animation with Block的方式来实现UILabel来回滚动。代码如下:

    -(void)startAnimationIfNeeded{
        //取消、停止所有的动画
        [self.aUILabel.layer removeAllAnimations];
        CGSize textSize = [self.aUILabel.text sizeWithFont:self.aUILabel.font];
        CGRect lframe = self.aUILabel.frame;
        lframe.size.width = textSize.width;
        self.aUILabel.frame = lframe;
        const float oriWidth = 180;
        if (textSize.width > oriWidth) {
            float offset = textSize.width - oriWidth;
            [UIView animateWithDuration:3.0
                                  delay:0
                                options:UIViewAnimationOptionRepeat //动画重复的主开关
             |UIViewAnimationOptionAutoreverse //动画重复自动反向,需要和上面这个一起用
             |UIViewAnimationOptionCurveLinear //动画的时间曲线,滚动字幕线性比较合理
                             animations:^{
                                 self.aUILabel.transform = CGAffineTransformMakeTranslation(-offset, 0);
                             }
                             completion:^(BOOL finished) {
                                 
                             }
             ];
        }
    }
    

在iOS项目中使用SDWebImage进行网络图片加载

SDWebImage是以Category的形式对UIImageView进行扩展。
是git上的一个开源项目:https://github.com/rs/SDWebImage

使用了它之后,让UIImageView可以直接设置图片的Url地址,剩下的下载,缓存就交给SDWebImage处理吧。

使用如下:

  1. 将下载的zip中的所有文件拷贝到项目文件夹中
  2. 将项目文件SDWebImage.xproj添加到自身的项目文件中
  3. 选中自身项目,在Target Dependencies中添加SDWebImage(一共有三个版本,根据需要选取一个)
  4. 在Link Binary With Library中,添加SDWebImage的.a静态库
  5. 在Link Binary With Library中,添加ImageIO.framework
  6. 在”Build Settings”–“Other Linker Flags”中添加”-ObjC”
  7. 在需要使用它的文件中包含h文件:
    #import 
  8. 然后使用很简单:
    [imageView setImageWithURL:aUrl];

iOS截屏

在iOS开发中,遇到了一个需要截取当前窗口并保存在相册中的问题。代码如下:

    CGSize imageSize = CGSizeMake(320, 480 - 88);
    //支持retina就靠这句话,不带参数的begin保存的图片分辨率很低的。
    if (UIGraphicsBeginImageContextWithOptions != NULL) {
        UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0.0);
    } else {
        UIGraphicsBeginImageContext(imageSize);
    }
    [self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);

在iOS开发中利用GCD进行多线程编程

GCD = Grand Central Dispatch,是苹果开发中针对多线程编程的一个框架,在iOS4.0中引入。



>>>>>>>>iOS中的多线程有以下几个办法
1.performSelector(InBackground or MainThread)
这个方法比较方便,但是问题在于参数传递只能支持一个对象(传多个参数,我是将其打包在一个NSDictionary里面)
2.NSOperationQueue
这个方法稍微复杂,提供了每个任务的封装(NSOperation)。可以继承NSOperation之后,在main函数中写一些同步执行的代码,然后放到一个Queue之中,Queue自动管理Operation的执行和调度(在UI线程以外)。对于异步执行的代码,需要重载NSOperation的好几个函数才能正常工作(告诉Queue关于这个任务的进度以及执行情况)。
3.NSThread
这种方法我还没有研究过,不过直觉会比较复杂。
4.GCD
在UI线程和其它线程之间切换很方便,我喜欢的方式是和NSOperationQueue搭配使用。本文着重介绍这个方法。



>>>>>>>>GCD的使用方法
以点击一个按钮,然后显示loading,同时在后台下载一张图片,最后将下载的图片放到UIImageView中显示为例。

  1.     //显示loading
  2.     self.indicator.hidden = NO;
  3.     [self.indicator startAnimating];
  4.     //进入异步线程
  5.     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  6.         //异步下载图片
  7.         NSURL * url = [NSURL URLWithString:@“http://anImageUrl”];
  8.         NSData * data = [NSData dataWithContentsOfURL:url];
  9.         //网络请求之后进入主线程
  10.         dispatch_async(dispatch_get_main_queue(), ^{
  11.             //关闭loading
  12.             [self.indicator stopAnimating];
  13.             self.indicator.hidden = YES;
  14.             if (data) {//显示图片
  15.                 self.imageView.image = [UIImage imageWithData:data];
  16.             }
  17.         });
  18.     });

这样利用GCD可以把关于一个任务的代码都放在一起。而不是像采用第一种方法一样代码到处散落。



>>>>>>> 利用GCD延迟执行任务的方法

  1. // 延迟2秒执行:
  2.     double delayInSeconds = 2.0;
  3.     dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
  4.     dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
  5.         // code to be executed on the main queue after delay
  6.     });



>>>>>>> 创建自己的Queue

  1. dispatch_queue_t custom_queue = dispatch_queue_create(“customQueue”, NULL);
  2.     dispatch_async(custom_queue, ^{
  3.         //doing something in custom_queue
  4.     });
  5.     dispatch_release(custom_queue);



>>>>>>> 利用GCD并行多个线程并且等待所有线程结束之后再执行其它任务

  1.     dispatch_group_t group = dispatch_group_create();
  2.     dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
  3.         // 并行执行的线程一
  4.     });
  5.     dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
  6.         // 并行执行的线程二
  7.     });
  8.     dispatch_group_notify(group, dispatch_get_global_queue(0,0), ^{
  9.         // 汇总结果
  10.     });



参考
http://blog.iosxcode4.com/archives/212

iOS开发中使用NSOperationQueue进行多线程操作

NSOperationQueue是iOS的SDK中提供的一个非常方便的多线程机制,用它来开发多线程非常简单。

可以把它视为一个线程池,还可以调用方法
-(void)setMaxConcurrentOperationCount:maxConcurrentNumber
来设置它的并行程度,默认为-1,即最大并行。

还可以通过NSOperation的方法来指定并行的操作之间的依赖关系:

  1. [theLatterTask addDependency:theBeforeTask];

在一个队列之中,可以加入NSOperation来指定执行的任务:
1.可以重载NSOperation的main方法来指定操作;
2.可以使用NSInvokeOperation通过指定selector和target来指定操作;
3.可以使用NSBlockedOperation通过Block来指定操作
这三个方法都非常方便。

以下是一个例子:

  1. _tasksQueue=[[NSOperationQueue alloc] init]; 
  2.     
  3. NSBlockOperation *getImageTask = [NSBlockOperation blockOperationWithBlock:^{
  4.         UIImage * image = nil;
  5.         NSData *imgData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:imageUrl]] returningResponse:nil error:nil];
  6.         if (imgData >> imgData.length > 0) {
  7.             image = [UIImage imageWithData:imgData];
  8.         }
  9.     }];
  10.  
  11. [_tasksQueue addOperation:getImageTask];

iOS (objective-c) 中的多线程互斥同步问题

在iOS中有几种方法来解决多线程访问同一个内存地址的互斥同步问题:

方法一,@synchronized(id anObject),(最简单的方法)

会自动对参数对象加锁,保证临界区内的代码线程安全

  1. @synchronized(self) {
  2.         // 这段代码对其他 @synchronized(self) 都是互斥的
  3.         // self 指向同一个对象
  4. }

方法二,NSLock

NSLock对象实现了NSLocking protocol,包含几个方法:
lock,加锁
unlock,解锁
tryLock,尝试加锁,如果失败了,并不会阻塞线程,只是立即返回NO
lockBeforeDate:,在指定的date之前暂时阻塞线程(如果没有获取锁的话),如果到期还没有获取锁,则线程被唤醒,函数立即返回NO
比如:

  1. NSLock *theLock = [[NSLock alloc] init]; 
  2.  if ([theLock lock]) {
  3.     //do something here
  4.     [theLock unlock]; 
  5. } 

方法三,NSRecursiveLock,递归锁

NSRecursiveLock,多次调用不会阻塞已获取该锁的线程。

  1. NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init]; 
  2. void MyRecursiveFunction(int value) { 
  3.     [theLock lock]; 
  4.     if (value != 0) { 
  5.         value; 
  6.         MyRecursiveFunction(value); 
  7.     }
  8.     [theLock unlock]; 
  9. } 
  10. MyRecursiveFunction(5);

方法四,NSConditionLock,条件锁

NSConditionLock,条件锁,可以设置条件

  1. //公共部分
  2. id condLock = [[NSConditionLock alloc] initWithCondition:NO_DATA]; 
  3.  
  4. //线程一,生产者
  5. while(true) { 
  6.     [condLock lockWhenCondition:NO_DATA]; 
  7.     //生产数据
  8.     [condLock unlockWithCondition:HAS_DATA]; 
  9. }
  10.  
  11. //线程二,消费者
  12. while (true) { 
  13.     [condLock lockWhenCondition:HAS_DATA]; 
  14.     //消费
  15.     [condLock unlockWithCondition:NO_DATA]; 
  16. }

方法五,NSDistributedLock,分布锁

NSDistributedLock,分布锁,文件方式实现,可以跨进程
用tryLock方法获取锁。
用unlock方法释放锁。
如果一个获取锁的进程在释放锁之前挂了,那么锁就一直得不到释放了,此时可以通过breakLock强行获取锁。

参考:

http://blog.sina.com.cn/s/blog_72819b170101590n.html

iOS (objective-c) 中的异常处理

objective-c中的异常处理很简单,经典的try,catch,final。需要注意的是,只能抛出NSObject对象。在cache块中还能继续用@throw进一步向外抛出异常。

  1. @try {
  2.     dangerousAction();
  3. } @catch (MyException* e) {
  4.     doSomething();
  5. } @catch (NSException* e) {
  6.     doSomethingElse();
  7.     @throw // 重新抛出异常
  8. } @finally {
  9.     cleanup();
  10. }

参考:
http://www.devbean.info/2011/04/from_cpp_to_objc_17/

从短url获取正常url(php,objective-c ios)

微博的兴起将很多长的url转换成了短url地址,但是有时候我们需要先通过短url拿到长url,再进行进一步的处理。

ios方法

  1. +(NSString*)unshortUrl🙁NSString*)shortUrl{
  2.     shortUrl = @“http://rrurl.cn/xxoo”;
  3.     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:shortUrl]];
  4.     NSHTTPURLResponse *response = [[[NSHTTPURLResponse alloc] init] autorelease];
  5.     [NSURLConnection sendSynchronousRequest:request returningResponse:>response error:nil];
  6.     return response.URL.absoluteString;
  7. }

php方法

  1. $url = “http://rrurl.cn/xxoo”; 
  2. echo unshorten($url); 
  3. function unshorten($url) { 
  4. $url = trim($url); 
  5. $headers = get_headers($url); 
  6. $location = $url; 
  7. $short = false; 
  8. foreach($headers as $head) { 
  9. if($head==“HTTP/1.1 302 Found”) $short = true; 
  10. if($short >> startwith($head,“Location: “)) { 
  11. $location = substr($head,10); 
  12. } 
  13. } 
  14. return $location; 
  15. } 

UIWebView背景透明的方法

想要UIWebView的背景透明,需要以下3步:

step1.设置webview的opaque=NO

step2.设置webview的backgroundColor = [UIColor clearColor];

step3.加载这样的内容到webView:

  1. <html><body style=“backgroundcolor:transparent;color:#ffffff”>具体内容</body></html>

关于人人开放平台中的Like.like接口

人人开放平台的Like.like接口,是用于标记用户喜欢的内容,相当于新浪微博的收藏。
发现这个接口总返回成功,确没有“喜欢”成功。
在调试了很久以后,终于发现它的规律:
主要是其url参数:其中需要的是resourceId,ownerID,type,这中间大有猫腻(再次不得不说一下人人的api之混乱)

对于share类型的
包括日志share(代码21),相册share(代码33)需要传送的是source_id,actor_id和@”share”

对于上传照片(30)类型
需要传送的是attachement下的meida_id,owner_id和media_type

对于发布日志(20)类型
需要传送的是source_id,actor_id和@”blog”

这样才能“喜欢”成功,刷新“首页”或者“个人主页”可以看到效果,但是要在”喜欢列表“里面看到内容的话,貌似需要一天以后···额···并且人人没有提供获取这个列表的api···

iOS开发中的键盘高度变化处理

在ios开发中,键盘很常用。在sdk版本5.0以前,键盘高度是固定值216px;5.0出来以后,键盘高度会随着键盘语言变化(中文要高些),在这种情况下一般而言对于界面需要重新布局。方法是利用NSNotificationCenter。

UIKeyboardWillShowNotification;
UIKeyboardDidShowNotification; 
UIKeyboardWillHideNotification; 
UIKeyboardDidHideNotification;

这几个notification是5.0sdk之前就有的,顾名思义就知道意思了。

UIKeyboardWillChangeFrameNotification  __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_5_0);
UIKeyboardDidChangeFrameNotification   __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_5_0);

这两个是sdk 5.0以后出来的,用来处理键盘高度的变化。

使用方法是:首先在notification注册观察者,比如:

if([[[UIDevice currentDevice] systemVersion] floatValue] >= 5.0) {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
}

当键盘高度将要变化时,就会收到通知,在通知的参数中可以得到键盘目前的高度和变化的目标高度,比如:

-(void)keyboardWillChangeFrame:(NSNotification*)notif{
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_2  
    NSValue *keyboardBoundsValue = [[notif userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey];  
#else  
    NSValue *keyboardBoundsValue = [[notif userInfo] objectForKey:UIKeyboardBoundsUserInfoKey];  
#endif
    CGRect keyboardEndRect = [keyboardBoundsValue CGRectValue];
    CGRect inputFrame = self.feedBackTextView.frame;
    //kb 216 vs textFrame 185
    float delta = keyboardEndRect.size.height - 216;
    float originalHeight = inputFrame.size.height;
    inputFrame.size.height = 185 - delta;
    if (inputFrame.size.height != originalHeight) {
        self.feedBackTextView.frame = inputFrame;
        self.feedBackBackgroundView.frame = inputFrame;
    }
}

另外一些从notification.userInfo中可以取得的key如下:

UIKeyboardFrameBeginUserInfoKey        // NSValue of CGRect
UIKeyboardFrameEndUserInfoKey          // NSValue of CGRect
UIKeyboardAnimationDurationUserInfoKey // NSNumber of double
UIKeyboardAnimationCurveUserInfoKey    // NSNumber of double



notif中userInfo的完整信息如下

keyboardChange:{
    UIKeyboardAnimationCurveUserInfoKey = 0;
    UIKeyboardAnimationDurationUserInfoKey = "0.25";
    UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 216}}";
    UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 372}";
    UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 588}";
    UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";
    UIKeyboardFrameChangedByUserInteraction = 0;
    UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 480}, {320, 216}}";
}



下面是一个完整的解决方案,用户需要知道键盘高度的细致变化

#pragma mark Keyboard
-(void)keyboardWillChangeFrame:(NSNotification*)notif{
    NSLog(@"keyboardChange:%@",[notif userInfo]);
    float keyboadHeightBegin = 0;
    float keyboadHeightEnd = 0;
    float animationDuration = [[[notif userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
    UIViewAnimationCurve animationCurve = [[[notif userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue];
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_2
    CGRect keyboardBeginFrame = [[[notif userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
    CGRect keyboardEndFrame = [[[notif userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    keyboadHeightBegin = 480 - keyboardBeginFrame.origin.y;
    keyboadHeightEnd = 480 - keyboardEndFrame.origin.y;
#else
    //these deprecated after iOS 3.2
    CGRect keyboardBounds = [[[notif userInfo] objectForKey:UIKeyboardBoundsUserInfoKey] CGRectValue];
    CGPoint keybordCenterBegin = [[[notif userInfo] objectForKey:UIKeyboardCenterBeginUserInfoKey] CGPointValue];
    CGPoint keybordCenterEnd = [[[notif userInfo] objectForKey:UIKeyboardCenterEndUserInfoKey] CGPointValue];
    keyboadHeightBegin = 480 - (keybordCenterBegin.y - keyboardBounds.size.height / 2);
    keyboadHeightEnd = 480 - (keybordCenterEnd.y - keyboardBounds.size.height / 2);
#endif
    NSLog(@"keyboardHeightChangeFrom:%.2f,To:%.2f",keyboadHeightBegin,keyboadHeightEnd);
    return;
    if (keyboadHeightEnd > 0) {
        //keyboard show or change frame
        [UIView animateWithDuration:animationDuration delay:0 options:animationCurve animations:^{
        } completion:^(BOOL finished) {
        }];
    } else {
        //keyboard hide
    }
}
-(void)keyboardDidChangeFrame:(NSNotification*)notif{
    //info like willChangeFrame
}
-(void)keyboardWillShow:(NSNotification*)notif{
    //keyboard height will be 216, on iOS version older than 5.0
    [UIView animateWithDuration:0.3f animations:^{
        self.contentTableView.height = 480 - 44 - 216;
    }];
}
-(void)keyboardWillHide:(NSNotification*)notif{
    [UIView animateWithDuration:0.3f animations:^{
        self.contentTableView.height = 480 - 44 - 28;
    }];
}
-(void)registerKeyboardEvent{
    float systemVer = [[[UIDevice currentDevice] systemVersion] floatValue];
    if(systemVer >= 5.0) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidChangeFrame:) name:UIKeyboardDidChangeFrameNotification object:nil];
    } else {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
    }
}
-(void)unregisterKeyboardEvent{
    if([[[UIDevice currentDevice] systemVersion] floatValue] > 5.0) {
        [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillChangeFrameNotification object:nil];
        [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidChangeFrameNotification object:nil];
    } else {
        [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
        [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];        
    }
}



下面这个解决方案就只考虑键盘出现和消失的处理

#pragma mark Keyboard
-(void)keyboardWillShow:(NSNotification*)notif{
    //keyboard height will be 216, on iOS version older than 5.0
    CGRect boxFrame = self.loginBoxView.frame;
    boxFrame.origin.y = 50;
    [UIView animateWithDuration:0.3f animations:^{
        self.loginBoxView.frame = boxFrame;
    }];
}
-(void)keyboardWillHide:(NSNotification*)notif{
    CGRect boxFrame = self.loginBoxView.frame;
    boxFrame.origin.y = 216;
    [UIView animateWithDuration:0.3f animations:^{
        self.loginBoxView.frame = boxFrame;
    }];
}
//在viewdidload中调用
-(void)registerKeyboardEvent{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
//在viewdidunload中调用
-(void)unregisterKeyboardEvent{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
}

利用JavaScript从UIWebView获取、修改、提交网页内数据的方法

在UIWebView的内容加载完之后,可以利用javascript获取其页面内的数据,核心就是通过UIWebView的方法:

NSString *string = [awebView stringByEvaluatingJavaScriptFromString:@"document.getElementById('field_2').value;" ];

以下是一些常用的js脚本:

thisURL = document.URL;
thisHREF = document.location.href;
thisSLoc = self.location.href;
thisDLoc = document.location;
thisTLoc = top.location.href;
thisPLoc = parent.document.location;
thisTHost = top.location.hostname;
thisHost = location.hostname;
thisTitle = document.title;
thisProtocol = document.location.protocol;
thisPort = document.location.port;
thisHash = document.location.hash;
thisSearch = document.location.search;
thisPathname = document.location.pathname;
thisHtml = document.documentElement.innerHTML;
thisBodyText = document.documentElement.innerText;//获取网页内容文字
thisBodyText = document.body.innerText;//获取网页内容文字

也可以通过同样的方法去设置页面内容(比如帮用户输入表单数据)
比如:
NSString *string = [awebView stringByEvaluatingJavaScriptFromString:@"document.getElementById('field_2').value='a value';" ];
就可以修改field_2的值了

同样也可以去模拟页面内按钮的点击,提交页面,比如:
document.getElementById('aButtonName').click();
或者,假设知道按钮是第几个input标签(假设为第一个)
document.getElementsByTagName('input').item(0).click();
也可以设置checkBox的状态:
document.getElementById('aCheckBoxId').checked=true;

参考:
http://hi.baidu.com/zfpp25/blog/item/0bc5e3565a7e632e0cf3e3d7.html
http://www.cnblogs.com/del/archive/2009/01/07/1370907.html
http://blog.csdn.net/studyrecord/article/details/6213843

使用sina开放平台的ios sdk做微博转发,调用repost接口返回auth faild错误

今天遇到一个问题,在调用statuses/repost.json接口的时候,总是返回auth faild错误。
但是发布微博是可以的,而且api文档中repost接口和upload接口是一个等级的,应该不是权限问题,于是各种茫然。
在尝试各种方法,搜索无果后,只能去sina api论坛提问(帖子传送门,问题已解决)
然后不幸的是,发现评论一条微博的接口@”comments/create.json”也存在同样问题。
在等待论坛回应的过程中,无意中发现一句代码:

postDataType:kWBRequestPostDataTypeMultipart

然后跟踪到正常工作的发布微博接口中去看看,发现微博发布接口中,有图片的时候,是用multipart,但是在只有text的时候,用的是:

postDataType:kWBRequestPostDataTypeNormal

于是把转发和评论都改成normal之后,正常了。。

———– 后话 ————-
其实一开始我尝试过一共三种postdatatype,都不正常,包括normal。。因为在当时还存在另一个bug,就是微博的id,返回类型是int64的,我一直当做string在用。所以在我设置成normal的时候,报错参数传递缺少(id)字段。。后来我把id打印出来,直接硬编码在代码中,终于转发成功了一次,这样才解决了auth faild的问题以及发现这个bug。。。

多个bug一起出现,更据迷惑性啊··

再后来,发现很多调用出现auth faild的错误原因都在于postDataType:kWBRequestPostDataTypeNormal没有设置正确。