用户工具

站点工具


ios_notes

目录

iOS 笔记

Resource

发布相关

首先要通过 CSR(Certificate Signing Request) 来取得 Development Certificate。然后就可以用开发证书给 app 做签名了。

一个 Provisioning profile 关联一个 App ID,一到多个开发证书,一到多个 UDID。

Provisioning profile 里面必然会有一个 App ID,如果你没有创建过 App ID,那必定是已经存在一个 Wildcard AppID。

AppleWWDRCA.cer 是 Apple Worldwide Developer Relations CA(Certification Authority) 的证书,所有开发者的证书都是用这个 CA 的私钥来签发的,所以这个 CA 的证书也必须导入到系统中来保证证书链的完整。

.ipa = app + provisioning profile

使用开发者授权是一套完整而且相对合理的流程,它可以分为下面几个阶段:

  1. 授权设备,将开发者授权与指定的设备绑定在一起,使得这些设备可以自由安装开发者发布的IPA。
  2. 生成授权文件.mobileprovision,这个文件会被打包进入IPA,实现设备与授权开发帐号的绑定。
  3. 生成证书文件.p12,这个文件也会被打包进入IPA,实现应用发行商的签名以及身份验证。

除此之外,开发者授权系统还提供了开发(Development)和发布(Distribution)两个不同的渠道。开发测试的时候需要设备的绑定而实现在设备上单独安装IPA,发布的时候则不需要绑定设备。所以开发与发布需要使用不同的.mobileprovision与.cer文件。

学习如何用 Storyboard 来写 UI(非常好的教程)

iOS 抓包分析

Crash 分析

A/V

Security

Objective-C 入门

iPhone 开发入门

Objective-C

protocol

类似于 C++ 的 pure virtual class 或者 Java 的 interface,但是其 method 可以是 required 或者 option 的。

@protocol MyProtocol
- (void)myProtocolMethod;
@end
 
@interface SubClass : SuperClass <MyProtocol> {
}
@end

category

用于在不使用继承的情况下扩展一个 class。

@interface BaseClass (CategoryName)
-(void) categoryMethod;
@end
 
@implementation BaseClass (CategoryName)
-(void) categoryMethod
{
	...
}
@end

@property(*) 括号中的属性内容介绍

readonly

此标记说明属性是只读的,默认的标记是读写,如果你指定了只读,在 @implementation 中只需要一个 getter。或者如果你使用 @synthesize 关键字,也只有 getter 方法被解析。而且如果你试图使用点操作符为属性赋值,你将得到一个编译错误。

readwrite

此标记说明属性会被当成读写的,这也是默认属性。setter 和 getter 都需要在 @implementation 中实现。如果使用 @synthesize 关键字,getter 和 setter 都会被解析。

assign

此标记说明设置器直接进行赋值,这也是默认值。在使用垃圾收集的应用程序中,如果你要一个属性使用 assign,且这个类符合 NSCopying 协议,你就要明确指出这个标记,而不是简单地使用默认值,否则的话,你将得到一个编译警告。这再次向编译器说明你确实需要赋值,即使它是可拷贝的。

retain

指定 retain 会在赋值时唤醒传入值的 retain 消息。此属性只能用于 Objective-C 对象类型,而不能用于 Core Foundation 对象。

copy

它指出,在赋值时使用传入值的一份拷贝。拷贝工作由 copy 方法执行,此属性只对那些实行了 NSCopying 协议的对象类型有效。

nonatomic

指出 accessor 不是原子操作,而默认地,accessor 是原子操作。这也就是说,在多线程环境下,解析的 accessor 提供一个对属性的安全访问,从 getter 得到的返回值或者通过 setter 设置的值可以一次完成,即便是别的线程也正在对其进行访问。如果你不指定 nonatomic,在自己管理内存的环境中,解析的 accessor 保留并自动释放返回的值,如果指定了 nonatomic,那么 accessor 只是简单地返回这个值。

strong

weak

For ARC,所指的 object 被销毁后,指针自动变 nil。多用于 outlet

unsafe_unretained

所指的 object 被销毁后,指针变野指针

UITabBarController & UINavigationController 合用

// 由于一般来说都是 TabBarController 里面套 NavigationController,所以要把这个 TabBarController 作为 window 的 rootViewController
UITabBarController *tabBarController = [[UITabBarController alloc] init];
[self.window setRootViewController:tabBarController];
[tabBarController release];
 
// 初始化 TabBarController 控制的第一个 ViewController,它是个 NavigationController
UINavigationController *controllerLeft = nil;
{
	// NavigationController 的 rootViewController 是个 FooController
	UIViewController *rootController = [[FooController alloc] init];
	controllerLeft = [[UINavigationController alloc] initWithRootViewController:rootController];
	[rootController release];
 
	// 每个被 TabBarController 所管辖的 ViewController 都有一个 UITabBarItem,可以用来设置 title 和 image
	UITabBarItem *tabBarItem = [[UITabBarItem alloc] init];
	{
		[tabBarItem setTitle:@"Left"];
		[controllerLeft setTabBarItem:tabBarItem];
	}
	[tabBarItem release];
}
 
// TabBarController 控制的第二个 ViewController 是个自定义 ViewController
UIViewController *controllerRight = [[BarController alloc] init];
 
NSArray *controllers = [[NSArray alloc] initWithObjects:controllerLeft, controllerRight, nil];
[controllerLeft release];
[controllerRight release];
 
// 设置 TabBarController 所控制的 ViewController
[tabBarController setViewControllers:controllers];

Devices

iPhone

  • 320 x 480
  • 163ppi

iPhone Retina

  • 640 x 960
  • 326ppi

iPhone 5

  • 640 x 1136
  • 326ppi

iPad

  • 768 x 1024
  • 132ppi

iPad Retina

  • 1536 x 2048
  • 264ppi

iPad mini

  • 768 x 1024
  • 163ppi

Command Line Tools

合并多个 .a 静态库

lipo -create libinput1.a libinput2.a -output liboutput.a

查看 .a 静态库信息

lipo -info libfoo.a

Sandbox 目录结构

Documents             - iTunes 会备份此目录

Library
    |
    +----Caches       - iTunes 不会备份此目录
    |
    +----Preferences  - iTunes 会备份此目录;通过 NSUserDefaults 访问

tmp                   - iTunes 不会备份此目录

xxxxx.app             - iTunes 会备份此目录

获取目录:

// Documents
NSArray  *paths         = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES );
NSString *documentsPath = [paths objectAtIndex:0];
 
// Library - NSLibraryDirectory
 
// Caches - NSCachesDirectory
 
// tmp
NSString *tempPath = NSTemporaryDirectory();
 
// path to example.png in the main bundle
NSString *path = [[NSBundle mainBundle] pathForResource:@"example" ofType:@"png"];

AirPlay

MDM

基础控件

UILabel

文本内容居中

[label setTextAlignment:NSTextAlignmentCenter];

UIImageView

显示方式

通过设置 imageView.contentMode 来实现:

  • UIViewContentModeScaleToFill - image 不按比例充满 imageView
  • UIViewContentModeScaleAspectFit - image 按比例缩放,image 的最长边充满 imageView,但是短边之外可能留白
  • UIViewContentModeScaleAspectFill - image 按比例充满 imageView,图像可能溢出 imageView 之外,用 clipsToBounds 来约束

UITableView

禁止某一行被编辑

方法一:重载 UITableViewDataSource 的 tableView:canEditRowAtIndexPath: 返回 NO 即可

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
	// ... ...
}

方法二:重载 UITableViewDelegate 的 tableView:editingStyleForRowAtIndexPath: 返回 UITableViewCellEditingStyleNone 即可

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView
           editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
	// ... ...
}

UIAlertView

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Title" 
                                                message:@"Message" 
                                               delegate:nil 
                                      cancelButtonTitle:@"OK"
                                      otherButtonTitles:nil];
[alert show];

UITextField

得到内容改变的通知

{
	[textField addTarget:self
                      action:@selector( onTextFieldEditingChanged:)
            forControlEvents:UIControlEventEditingChanged];
}
 
- (void)onTextFieldEditingChanged:(id)sender
{
	// ... ...
}

用户按回车键后得到通知

{
	[textField setReturnKeyType:UIReturnKeyDone];
	[textField setDelegate:self];  // Refer to UITextFieldDelegate
}
 
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
	// do what you want to do ...
	return( NO );
}

UITextView

得到内容改变的通知

{
	[textView setDelegate:self];  // UITextViewDelegate
}
 
- (void)textViewDidChange:(UITextView *)textView
{
	// ... ...
}

UITabBarController

UI 结构

  • UITabBarController
    • view - UILayoutContainerView - 屏幕大小
      • UITransitionView - 屏幕高度 - 49
      • UITabBar - 高度 49

UINavigationController

UI 结构

  • UINavigationController
    • view - UILayoutContainerView - 屏幕大小
    • UINavigationTransitionView - 屏幕大小
    • UINavigationBar - 高 44,y 坐标 20(位于 status bar 之下)

在 ViewController 间切换的几种方法

UINavigationController

假设要从 A 切换到 B,A 的 title 太长的话,B 的 back 按钮会把 B 的 tile 推到右边,这个问题可以通过在 A 的 viewDidLoad: 里加入如下 code 解决:

UIBarButtonItem *back = [[[UIBarButtonItem alloc] initWithTitle:@"Back"
                                                          style:UIBarButtonItemStyleBordered
                                                         target:nil
                                                         action:nil] autorelease];
self.navigationItem.backBarButtonItem = back;

presentModalViewController

- (void)presentModalViewController:(UIViewController *)modalViewController
                          animated:(BOOL)animated;
 
// dismissModalViewControllerAnimated: 是给 presenting view controller 用的,
// 不是给 presented view controller 用的。例如 A presentModalViewController:
// 了 B,应该 [A dismissModalViewControllerAnimated:YES] 把 B dismiss 掉,而不
// 是 [B dismissModalViewControllerAnimated:YES] 把自己 dismiss 掉。
- (void)dismissModalViewControllerAnimated:(BOOL)animated;

假设 FooViewController 要 presentModalViewController BarViewController,那么当 Bar 完成了他自己的任务之后如何把结果数据(如果有的话)传回给 Foo 呢?

Bar 可以定义自己的 protocol,然后 Foo 实现这个 protocol,并作为 Bar 的 delegate,当 Bar 做完工作以后调用 delegate 实现的方法,这样 Foo 就获得了控制权,同时也得到了 Bar 传给它的数据。

@class BarViewController;
 
@protocol BarViewControllerDelegate<NSObject>
- (void)barDone:(BarViewController *)controller withData:(NSString *)data;
@end
 
@interface BarViewController : UIViewController
@property(nonatomic, weak) id<BarViewControllerDelegate> delegate;  // ARC 用 weak;非 ARC 用 assign
// ... ...
@end

在 Bar 的实现里:

@implementation BarViewController
 
@synthesize delegate = _delegate;
 
- (IBAction)done:(id)sender
{
	[self dismissModalViewControllerAnimated:YES];
 
	if( [delegate respondsToSelector:@selector( barDone:withData: )] )
		[delete barDone:self withData:@"bar"];
}
 
@end

在 Foo 的实现里:

@implementation FooViewController
 
- (IBAction)do:(id)sender
{
	BarViewController *bar = [[BarViewController alloc] init];
	[bar setDelegate:self];
	[self presentModalViewController:bar animated:YES];
}
 
- (void)barDone:(BarViewController *)controller withData:(NSString *)data
{
	NSLog( @"%@", data );
}
@end

addChildViewController

New APIs in iOS 5.0: addChildViewController:, removeFromParentViewController:, transitionFromViewController:toViewController:duration:options:animations:completion:, willMoveToParentViewController:, didMoveToParentViewController:

addChildViewController: 以后再 transitionFromViewController:

Animation

beginAnimations/commitAnimations

[UIView beginAnimations:/commitAnimations:] 跟 [CATransaction begin:/commit:] 有什么关系?UIView 的 animation transaction 底层通过 CATransaction 实现?

iOS 4.0 及以后版本推荐使用基于 block 的接口来代替 animation transaction:

+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations

add animation object into layer

资源相关

跨设备使用图片资源

  • button~iphone.png
  • button@2x~iphone.png
  • button~ipad.png
  • button@2x~ipad.png
[UIImage imageNames:@"button"];  // 不需要带扩展名

Debug

取得 crash log 的方法

App crash 之后,在下一次 device 被连接在 Mac 或者 PC 后,iTunes 会自动把 crash log 复制到 Mac/PC,在 Mac 上,可以进入如下位置取得 crash log:

~/Library/Logs/CrashReporter/MobileDevice

对于 Windows XP,crash log 位于如下位置:

C:\Documents and Settings\<user_name>\Application Data\Apple computer\Logs\CrashReporter

对于其他 Windwos 版本,crash log 位于如下位置:

C:\Users\<user_name>\AppData\Roaming\Apple computer\Logs\CrashReporter/MobileDevice

Misc

App 在模拟器中的位置

~/Library/Application Support/iPhone Simulator/5.1/Applications/{GUID}/

从任何地方获取 application delegate 的方法

AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];

从 Info.plist 里取版本号

NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
NSString *version = [infoDict objectForKey:@"CFBundleVersion"];

把顶端状态栏变成黑色

在 Info.plist 里增加一项 Status bar style,值设置为 Opaque black style,这样做可以让黑色状态栏在程序启动后立即生效,还可以通过代码的方式做到:

[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleBlackOpaque];

但是会看到一个变色的过程。

用浏览器打开 URL

[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://www.google.com"]];

打电话

[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"tel:4083334444"]];

发短信

#import <MessageUI/MFMessageComposeViewController.h> 并引入 MessageUI.framework。用如下 code 调出发送短信的 UI:

{
	MFMessageComposeViewController *messageUI = [[MFMessageComposeViewController alloc] init];
	[messageUI setRecipients:[NSArray arrayWithObject:@"4083334444"]];
	[messageUI setBody:@"test"];
	[messageUI setMessageComposeDelegate:self];  // 需要实现 MFMessageComposeViewControllerDelegate
	[self presentModalViewController:messageUI animated:YES];
}
 
- (void)messageComposeViewController:(MFMessageComposeViewController *)controller
                 didFinishWithResult:(MessageComposeResult)result
{
	[controller dismissModalViewControllerAnimated:YES];
}

MFMessageComposeViewController 自己本身就是一个 NavigationController,所以不能把它推入其它 NavigationController,而是要用 presentModalViewController 来显示。

发邮件

#import <MessageUI/MFMailComposeViewController.h>。用如下 code 调出发邮件的 UI:

{
	MFMailComposeViewController *emailUI = [[MFMailComposeViewController alloc] init];
	[emailUI setToRecipients:[NSArray arrayWithObject:@"test@gmail.com"]];
	[emailUI setSubject:@"Hello"];
	[emailUI setMessageBody:@"etst" isHTML:NO];
	[emailUI setMailComposeDelegate:self];  // 需要实现 MFMailComposeViewControllerDelegate
	[self presentModalViewController:emailUI animated:YES];
}
 
- (void)mailComposeController:(MFMailComposeViewController *)controller
          didFinishWithResult:(MFMailComposeResult)result
                        error:(NSError*)error
{
	[controller dismissModalViewControllerAnimated:YES];
}

访问 Calendar

用代码来区分 iPad 和 iPhone(iOS 3.2 以上)

if( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad )
{
	// The device is an iPad running iOS 3.2 or later
}
else
{
	// The device is an iPhone or iPod touch
}

去掉 UITableViewCell 的边框

[cell setBackgroundView:[[UIView alloc] initWithFrame:CGRectZero]];

给任意一个 UIView 加上手势识别(获得点击事件)

{
	UITapGestureRecognizer *tap = [[[UITapGestureRecognizer alloc] initWithTarget:self
                                                                               action:@selector(onTap:)] autorelease];
	[view addGestureRecognizer:tap];
}
 
- (void)onTap:(UIGestureRecognizer *)gesture
{
	//... ...
}

某些 view 需要把 userInteractionEnabled 设置为 YES 后,手势 action 才会被调用,如 UIImageView。

截取部分图片的代码

CGImageRef smallImageRef = CGImageCreateWithImageInRect( image.CGImage, rect );
CGRect smallBounds = CGRectMake( 0, 0, CGImageGetWidth( smallImageRef ), CGImageGetHeight( smallImageRef ) );
 
UIGraphicsBeginImageContext( smallBounds.size );
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage( context, smallBounds, smallImageRef );
UIImage *smallImage = [UIImage imageWithCGImage:smallImageRef];
UIGraphicsEndImageContext();

ARC

在非 ARC 工程里使用 ARC 库:

  • -fobjc-arc

在 ARC 工程里使用非 ARC 库:

  • -fno-objc-arc

屏幕方向的判断

有两种方法:

if( self.interfaceOrientation == UIDeviceOrientationPortrait ||
	self.interfaceOrientation == UIDeviceOrientationPortraitUpsideDown )
{
	;
}
else
{
	;
}
if( [UIDevice currentDevice].orientation == UIDeviceOrientationPortrait ||
	[UIDevice currentDevice].orientation == UIDeviceOrientationPortraitUpsideDown )
{
	;
}
else
{
	;
}

区别在于,方法一用:

  • UIInterfaceOrientationPortrait
  • UIInterfaceOrientationPortraitUpsideDown
  • UIInterfaceOrientationLandscapeLeft
  • UIInterfaceOrientationLandscapeRight

方法二用:

  • UIDeviceOrientationUnknown
  • UIDeviceOrientationPortrait
  • UIDeviceOrientationPortraitUpsideDown
  • UIDeviceOrientationLandscapeLeft
  • UIDeviceOrientationLandscapeRight
  • UIDeviceOrientationFaceUp
  • UIDeviceOrientationFaceDown

其实 UIInterfaceOrientationXxxxx 里的值跟 UIDeviceOrientationXxxxx 里的值是一一对应的:

typedef NS_ENUM(NSInteger, UIInterfaceOrientation)
{
    UIInterfaceOrientationPortrait           = UIDeviceOrientationPortrait,
    UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
    UIInterfaceOrientationLandscapeLeft      = UIDeviceOrientationLandscapeRight,
    UIInterfaceOrientationLandscapeRight     = UIDeviceOrientationLandscapeLeft
};

但是用第二种方法取到的值更多,如果用第二种方法来判断屏幕横竖的话,会取到 UIDeviceOrientationFaceUp 这种值,这对 UI 一般来说是没有意义的,所以一般用方法一就好了。

得到键盘出现和消失的通知

// 键盘出现
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillShow:)
                                             name:UIKeyboardWillShowNotification
                                           object:nil];
// 键盘消失
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillHide:)
                                             name:UIKeyboardWillHideNotification
                                           object:nil];
- (void)keyboardWillShow:(NSNotification *)notification
{
	NSDictionary *userInfo = [notification userInfo];
 
	NSValue *animationDurationValue = [userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];
	NSTimeInterval animationDuration;
	[animationDurationValue getValue:&animationDuration];
 
	NSValue *keyboardRectValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
	CGRect keyboardRect = [keyboardRectValue CGRectValue];
 
	[UIView animateWithDuration:animationDuration
					 animations:^
	{
		CGRect temp = self.navigationController.toolbar.frame;
		if( self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft ||
		   self.interfaceOrientation == UIInterfaceOrientationLandscapeRight )
		{
			temp.origin.y = [UIScreen mainScreen].bounds.size.width - keyboardRect.size.width - temp.size.height - [[UIApplication sharedApplication] statusBarFrame].size.width;
		}
		else
		{
			temp.origin.y = [UIScreen mainScreen].bounds.size.height - keyboardRect.size.height - temp.size.height - [[UIApplication sharedApplication] statusBarFrame].size.height;
		}
		self.navigationController.toolbar.frame = temp;
	}];
}

点击 UIView 后得到通知

{
	UIView *foo = [[UIView alloc] init];
	UITapGestureRecognizer *tap = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapView:)] autorelease];
	[foo addGestureRecognizer:tap];
}
 
- (void)onTapView:(UIGestureRecognizer *)agesture
{
	// ... ...
}

判断两个时间戳是否属于同一天

// timestamp1 = time( NULL );
int secondsFromGMT = [[NSTimeZone localTimeZone] secondsFromGMT];
if( (int)( ( timestamp1 + secondsFromGMT ) / 86400 ) == (int)( ( timestamp2 + secondsFromGMT ) / 86400 ) )
{
	// In the same day
}
else
{
	// Not in the same day
}

一段格式化时间的 code

http://hi.baidu.com/cxh1984/blog/item/19ab3c126a96354ef919b8ee.html

/**
 格式化时间
 timeSeconds 为0时表示当前时间,可以传入你定义的时间戳
 timeFormatStr为空返回当当时间戳,不为空返回你写的时间格式(yyyy-MM-dd HH:ii:ss)
 setTimeZone ([NSTimeZone systemTimeZone]获得当前时区字符串)
 */
- (NSString *)setTimeInt:(NSTimeInterval)timeSeconds
           setTimeFormat:(NSString *)timeFormatStr
             setTimeZone:(NSString *)timeZoneStr
{
	NSString *date_string;
	NSDate   *time_str;
 
	if( timeSeconds > 0 )
	{
		time_str =[NSDate dateWithTimeIntervalSince1970:timeSeconds];
	}
	else
	{
		time_str=[[NSDate alloc] init];
	}
 
	if( timeFormatStr == nil )
	{
		date_string = [NSString stringWithFormat:@"%d",(long)[time_str timeIntervalSince1970]];
	}
	else
	{
		NSDateFormatter *date_format_str =[[[NSDateFormatter alloc] init] autorelease];
		[date_format_str setDateFormat:timeFormatStr];
		if( timeZoneStr!=nil)
		{
			[date_format_str setTimeZone:[NSTimeZone timeZoneWithName:timeZoneStr]];
		}
		date_string =[date_format_str stringFromDate:time_str];
	}
 
	return( date_string );
}
 
/**
 *用法
*/
-(void)viewWillAppear:(BOOL)animated
{
	NSString *a =[self setTimeInt:1317914496 setTimeFormat:@"yy.MM.dd HH:mm:ss" setTimeZone:nil];
	NSString *b =[self setTimeInt:0 setTimeFormat:@"yy.MM.dd HH:mm:ss" setTimeZone:nil];
	NSString *c =[self setTimeInt:0 setTimeFormat:nil setTimeZone:nil];
	NSString *d =[self setTimeInt:0 setTimeFormat:@"yy.MM.dd HH:mm:ss" setTimeZone:@"GMT"];
 
	NSLog( @"%@,,,%@,,,%@,,,%@", a, b, c, d );
}
  • G: 公元时代,例如AD公元
  • yy: 年的后2位
  • yyyy: 完整年
  • MM: 月,显示为01-12
  • MMM: 月,显示为英文月份简写,如 Jan
  • MMMM: 月,显示为英文月份全称,如 Janualy
  • dd: 日,2位数表示,如02
  • d: 日,1-2位显示,如 2
  • EEE: 简写星期几,如Sun
  • EEEE: 全写星期几,如Sunday
  • aa: 上下午,AM/PM
  • H: 时,24小时制,0-23
  • K:时,12小时制,0-11
  • m: 分,1-2位
  • mm: 分,2位
  • s: 秒,1-2位
  • ss: 秒,2位
  • S: 毫秒

随机数

int i = arc4random();

arc4random() 是一个真正的伪随机算法,而且范围是 rand() 的两倍。在 iPhone 中,RAND_MAX 是 0x7fffffff (2147483647),而 arc4random() 返回的最大值则是 0x100000000 (4294967296),从而有更好的精度。此外,使用 arc4random() 还不需要生成随机种子,因为第一次调用的时候就会自动生成。

把 category 放在 static library 产生 selector not recognized 运行时错误的解决方法

在 static library 的工程设置里,在 linker flag 里添加“-ObjC”(一般都已自动添加)。

在 category 的实现文件里使用如下 macro:

#define TT_FIX_CATEGORY_BUG(name) @interface TT_FIX_CATEGORY_BUG_##name @end \
                                  @implementation TT_FIX_CATEGORY_BUG_##name @end

这个 macro 的参数 name 其实可以随便。

在使用这个 static library 的 app 的工程设置里,也要添加“-ObjC”。

翻转图片

UIImage *img = [[UIImage alloc] initWithCGImage:image.CGImage
                                          scale:1.0
                                    orientation:UIImageOrientationRight];

从 UIImage 得到图片像素数据

UIImage *img = [UIImage imageNamed:@"foo.png"];
CGImageRef imageRef = img.CGImage;
CGDataProviderRef dataProviderRef = CGImageGetDataProvider( imageRef );
CFDataRef dataRef = CGDataProviderCopyData( dataProviderRef );
const UInt8 *pixels = CFDataGetBytePtr( dataRef );
// do whatever you want
CFRelease( dataRef );

从内存块创建 UIImage

+ (UIImage *)foo
{
	unsigned char *rawData = (unsigned char *)malloc( rawDataSize );
 
	// fill rawData ...
 
	// 调用者有义务在使用完以后 call CGDataProviderRelease() 来释放 provider
	CGDataProviderRef provider = CGDataProviderCreateWithData(
		NULL,  // 这个自定义参数后面会传给 ProviderReleaseData()
		rawData, 
		rawDataSize, 
		ProviderReleaseData );  // 调用 CGDataProviderRelease() 时会被触发
 
	CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
 
	CGImageRef imageRef = CGImageCreate(
		imageDimension,  // width
		imageDimension,  // height
		BITS_PER_BYTE,   // 每个颜色通道几位
		bitsPerPixel,    // 每个像素几位
		bytesPerLine,    // 每个扫描线多少字节
		colorSpaceRef,
		kCGBitmapByteOrderDefault | kCGImageAlphaLast,
		provider,
		NULL,            // the decode array for the image
		NO,              // whether interpolation should occur
		kCGRenderingIntentDefault );
 
	UIImage *newImage = [UIImage imageWithCGImage:imageRef];
 
	CGImageRelease( imageRef );
	CGColorSpaceRelease( colorSpaceRef );
	CGDataProviderRelease( provider );  // 导致下面的 ProviderReleaseData() 被调用
 
	return( newImage );
}
 
void ProviderReleaseData( void *info, const void *data, size_t size )
{
	free( (void *)data );
}

AddressBook

使用地址簿需要引入 AddressBook.framework 和 AddressBookUI.framework。然后包含如下头文件:

#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>

显示地址薄供用户选择:

ABPeoplePickerNavigationController *peoplePicker = [[ABPeoplePickerNavigationController alloc] init];
peoplePicker.peoplePickerDelegate = self;  // delegate 需要实现 ABPeoplePickerNavigationControllerDelegate
[self presentModalViewController:peoplePicker animated:YES];

ABPeoplePickerNavigationControllerDelegate 的实现细节:

// 用户选择了一个联系人
// 返回 YES 会让地址簿进入该联系人的 detail page;返回 NO 会阻止这个行为
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
      shouldContinueAfterSelectingPerson:(ABRecordRef)person
{
	NSLog( @"First name = %@", ABRecordCopyValue( person, kABPersonFirstNameProperty ) );
	NSLog( @"Last name  = %@", ABRecordCopyValue( person, kABPersonLastNameProperty ) );
	ABMultiValueRef emails = ABRecordCopyValue( person, kABPersonEmailProperty );
	int emailCount = ABMultiValueGetCount( emails );
	for( int i = 0; i < emailCount; i++ )
	{
		NSString *emailLabel = (__bridge NSString *)ABAddressBookCopyLocalizedLabel( ABMultiValueCopyLabelAtIndex( emails, i ) );
		NSString *emailContent = (__bridge NSString *)ABMultiValueCopyValueAtIndex( emails, i );
		NSLog( @"%@ = %@", emailLabel, emailContent );
	}
 
	[peoplePicker dismissModalViewControllerAnimated:YES];
	return( NO );
}
 
// 用户在某联系人的 detail page 里点击了某一项(比如电话号码)
// 返回 YES 会让地址薄在该项上执行默认的动作,比如拨出一通电话;返回 NO 会阻止这个行为
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker 
      shouldContinueAfterSelectingPerson:(ABRecordRef)person
                                property:(ABPropertyID)property
                              identifier:(ABMultiValueIdentifier)identifier
{
	return( NO );
}
 
// 用户点了 Cancel
- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker
{
	[peoplePicker dismissModalViewControllerAnimated:YES];
}

Regular Expression

NSRegularExpression - iOS 4.0 后可用

NSRegularExpressionOptions:

  • NSRegularExpressionCaseInsensitive - 忽略大小写
  • NSRegularExpressionAllowCommentsAndWhitespace - 忽略 RE 中以 # 开头的注释和空白字符
  • NSRegularExpressionIgnoreMetacharacters -
  • NSRegularExpressionDotMatchesLineSeparators - 允许用 . 来匹配换行符,默认不可以
  • NSRegularExpressionAnchorsMatchLines - 允许用 ^ 和 $ 来匹配行首和行尾
  • NSRegularExpressionUseUnixLineSeparators - 只把 \n 当做换行符(否则的话可以允许所有标准的换行符)
  • NSRegularExpressionUseUnicodeWordBoundaries - 把 Uincode TR#29 当做词的分界(否则的话使用传统正则的词分界)
NSString *data = @"IP = 192.168.0.1";
 
NSError *error = nil;
NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:@"(\\d+)\\."
                                                                    options:NSRegularExpressionCaseInsensitive
                                                                      error:&error];
NSArray *matches = [re matchesInString:data
                               options:0
                                 range:NSMakeRange( 0, [data length] )];
 
NSLog( @"count = %lu", [matches count] );
for( NSTextCheckingResult *match in matches )
	for( int i = 0; i < [match numberOfRanges]; ++i )
		NSLog( @"NO.%d = %@", i, [data substringWithRange:[match rangeAtIndex:i]] );

上述代码的结果是:

count = 3
NO.0 = 192.
NO.1 = 192
NO.0 = 168.
NO.1 = 168
NO.0 = 0.
NO.1 = 0

由此可见,NSTextCheckingResult 的第 0 个 range 是整个 RE 匹配到的内容,第 1 个及以后的 range 才是 capture groups 捕获到的内容。

其它几个 NSRegularExpression 的方法用法相同:

  • numberOfMatchesInString:options:range: 返回 NSUInteger 的匹配次数
  • rangeOfFirstMatchInString:options:range: 返回 NSRange 的匹配范围
  • firstMatchInString:options:range 返回 NSTextCheckingResult 类型的第一个匹配结果
NSString *data = @"IP = 192.168.0.1";
NSError *error = nil;
NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:@"(\\d+)"
                                                                    options:NSRegularExpressionCaseInsensitive
                                                                      error:&error];
NSString *modifiedString = [re stringByReplacingMatchesInString:data
                                                        options:0
                                                          range:NSMakeRange( 0, [data length] )
                                                   withTemplate:@"($0)"];
NSLog( @"%@", modifiedString );  // IP = (192).(168).(0).(1)
NSString *data = @"IP = 192.168.0.1";
NSError *error = nil;
NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:@"(\\d+)"
                                                                    options:NSRegularExpressionCaseInsensitive
                                                                      error:&error];
 
__block NSUInteger count = 0;
[re enumerateMatchesInString:data
                     options:0
                       range:NSMakeRange( 0, [data length] )
                  usingBlock:^( NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop )
{
	NSLog( @"%@", [data substringWithRange:[match rangeAtIndex:1]] );
	if( ++count >= 2 )
		*stop = YES;
}];

上述代码将会输出:

192
168

如果有捕获组位于不确定的部分内,如下:

test123hafoo456
test789ha
test(\d+)ha(?:foo(\d+))?

那么最后得到的 NSTextCheckingResult 的 numberOfRanges 始终是 3。如果不确定部分存在,那么 rangeAtIndex:2 就是一个正常的 NSRange;如果不存在,那么改 NSRange 的 length 就是 0。

OTA

可以使用 php-tot:https://github.com/OpenFibers/php-tot

如何从 .app 生成 .ipa

.app 本质上是个文件夹,.ipa 本质上是个 zip 压缩包。

  1. 新建一个 Payload 目录
  2. 把 test.app 复制到 Payload 目录中
  3. Payload 外面还需要有一个名为 iTunesArtwork 的 512×512 没有扩展名的 JPEG 图片
  4. 把 Payload 目录跟 iTunesArtwork 压缩成 zip 包后把扩展名改成 .ipa

APNS - Apple Push Notification Service

  1. APNS 是免费的。只要有开发者账号便可以申请 APNS 证书
  2. APNS 又是不可靠的,苹果对信息推送的可靠性不做任何保证
  3. APNS 对消息的大小是有限制的,总容量不能超过 256 字节
  1. 用户第一次安装应用并第一次启动时,会弹出对话框提示应用需要开通推送,是否允许,如果允许,应用会得到一个硬件 token:
    1. 此 token 唯一与设备相关,同一设备上不同应用获取的 token 是一样的
    2. 当应用被卸载,然后重新安装时,确认对话框不会再出现,自动继承前一次安装的设置信息
    3. 推送设置可以在设置-通知中进行更改。可以选择开启消息框、声音以及 badge number 中的一种或多种
  2. 应用将收到的 token 发送到服务端,也就是 APNS 消息的源头
  3. 应用服务器通过 token 及证书向苹果的消息服务器发送消息
  4. 苹果将接收到的消息发送到对应设备上的对应应用
  5. 如果应用未处于 Active 状态(未启动或 background),默认设置下,屏幕顶部会弹出消息框,同时有声音提示,点击改消息框会进入应用,如不点击则应用图标上会有 badge number 出现

CFNetwork

Resource

Proxy

在 Wi-Fi HTTP Proxy 配置为 Off 的情况下,用 CFNetworkCopySystemProxySettings() 得到的 dictionary 内容如下:

{
	ExceptionsList =
	(
		"*.local",
		"169.254/16"
	);
	FTPPassive = 1;
	"__SCOPED__" =
	{
		en0 =
		{
			ExceptionsList =
			(
				"*.local",
				"169.254/16"
			);
		FTPPassive = 1;
		};
	};
}

在设置了手动代理 test:foo@192.168.0.1:1234 之后得到的结果是:

{
    ExceptionsList = (
        "*.local",
        "169.254/16"
    );
    FTPPassive = 1;
    HTTPEnable = 1;
    HTTPPort = 1234;
    HTTPProxy = "192.168.0.1";
    HTTPProxyAuthenticated = 1;
    HTTPProxyUsername = test;
    HTTPSEnable = 1;
    HTTPSPort = 1234;
    HTTPSProxy = "192.168.0.1";
    "__SCOPED__" = {
        en0 = {
            ExceptionsList = (
                "*.local",
                "169.254/16"
            );
            FTPPassive = 1;
            HTTPEnable = 1;
            HTTPPort = 1234;
            HTTPProxy = "192.168.0.1";
            HTTPProxyAuthenticated = 1;
            HTTPProxyUsername = test;
            HTTPSEnable = 1;
            HTTPSPort = 1234;
            HTTPSProxy = "192.168.0.1";
        };
    };
}

在设置了地址为 http://test 的自动代理之后得到的信息是:

{
    ExceptionsList = (
        "*.local",
        "169.254/16"
    );
    FTPPassive = 1;
    ProxyAutoConfigEnable = 1;
    ProxyAutoConfigURLString = "http://test";
    "__SCOPED__" = {
        en0 = {
            ExceptionsList = (
                "*.local",
                "169.254/16"
            );
            FTPPassive = 1;
            ProxyAutoConfigEnable = 1;
            ProxyAutoConfigURLString = "http://test";
        };
    };
}

让 CFNetwork 连接 HTTPS 站点时不检查站点证书链

CFReadStreamRef myStream = CFReadStreamCreateForHTTPRequest( kCFAllocatorDefault, myRequest );
 
CFMutableDictionaryRef myDict = CFDictionaryCreateMutable(
	kCFAllocatorDefault,
	0,
	&kCFTypeDictionaryKeyCallBacks,
	&kCFTypeDictionaryValueCallBacks );
CFDictionarySetValue( myDict, kCFStreamSSLValidatesCertificateChain, kCFBooleanFalse );
CFReadStreamSetProperty( myStream, kCFStreamPropertySSLSettings, myDict );
 
CFReadStreamOpen( myStream );

在网上还看到过跟 HTTPS 相关的其它 flag:

NSDictionary *settings = [[NSDictionary alloc] initWithObjectsAndKeys:
                          [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates,
                          [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot,
                          [NSNumber numberWithBool:NO],  kCFStreamSSLValidatesCertificateChain,
                          kCFNull,                       kCFStreamSSLPeerName,
                          nil];

NSString, NSData, NSArray, NSDictionary

字符串转数字

int    a = [string intValue];
float  b = [string floatValue];
double c = [string doubleValue];

URL 编码

NSString *encodedUrl = (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(
	kCFAllocatorDefault,
	(__bridge CFStringRef)url,
	NULL,
	CFSTR( ":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"),
        CFStringConvertNSStringEncodingToEncoding( NSUTF8StringEncoding ) );

URL 解码

// Decode a percent escape encoded string
NSString *decodedUrl = (__bridge_transfer NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(
	NULL,
	(__bridge CFStringRef)url,
	CFSTR(""),
	kCFStringEncodingUTF8 );
 
// another way
NSString *decodedUrl = [url stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

GCD (Grand Central Dispatch)

Sample Code

dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
	// 在并发线程中异步执行
});
 
dispatch_async( dispatch_get_main_queue(), ^{
	// 在主(UI)线程中异步执行
});
 
dispatch_queue_t dispatchQueue = dispatch_queue_create( "dispatchQueue", NULL );
dispatch_sync( dispatchQueue, ^{
	// 在某用户线程里同步执行
});

XML

NSXMLParser

NSNotificationCenter

注册通知

[[NSNotificationCenter defaultCenter] addObserver:self    // 谁是 observer?谁在将来会收到这里注册的这个通知?
                                         selector:@selector( execute: )  // 收到通知后触发什么方法
                                             name:@"foo"  // 注册的通知名称(标识)
                                           object:nil];   // 只对特定的通知 sender 做出响应,nil 表示不对 sender 做出限制
 
- (void)execute:(NSNotification *)notification
{
	if( [notification.name isEqualToString:@"foo"] )
	{
		// notification.userInfo
	}
}

发送通知

[[NSNotificationCenter defaultCenter] postNotificationName:@"foo"  // 通知名称
                                                    object:nil];   // sender
 
[[NSNotificationCenter defaultCenter] postNotificationName:@"foo"  // 通知名称
                                                    object:nil     // sender
                                                  userInfo:nil];   // 一个 NSDictionary * 类型的参数,observer 可以用 notification.userInfo 取出来

解除接收通知

observer 对通知不再感兴趣时应该从通知中心里解除对通知的关注:

[[NSNotificationCenter defaultCenter] removeObserver:self
                                                name:@"foo"
                                              object:nil];

Storyboard

实例化一个位于 Storyboard 中的 ViewController

假如 Storyboard 里有一个 static cell 的 UITableViewController,直接用如下 code 实例化这个 TableView 在运行期是看不到设计好的那些 static cell 的:

FooController *foo = [[FooController alloc] init];

而是要用如下 code:

//UIStoryboard *main = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];
FooController *foo = [self.storyboard instantiateViewControllerWithIdentifier:@"fooIdentifier"];

用代码触发 segue

[self performSegueWithIdentifier:@"doSomething" sender:self];

在 segue 触发时介入处理

重载如下方法:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

segue.destinationViewController 就是即将要转到的 ViewComtroller;segue.identifier 是该 segue 的标识。

ViewController 从 Storyboard 里激活的话,init: 或者 initWithStyle: 都不会被调用,而是 initWithCoder: 被调用,假如从 ViewController AAA 转到 BBB,那么被调用的方法顺序是:

  1. BBB 的 initWithCoder:
  2. BBB 的 awakeFromNib
  3. AAA 的 prepareForSegue

Custom Segue

Data Storage

NSUserDefaults

数据被存储至 App 的 Library/Preferences/[BUNDLE_ID].plist,而且这个位置的文件会被 iTunes 备份。

NSUserDefaults *userPrefs = [NSUserDefaults standardUserDefaults];

standardUserDefaults 是 NSUserDefaults 的类方法,创建出的 instance 是 singleton。

// set
[userPrefs setObject:@"a123456789" forKey:@"userID"];
[userPrefs setInteger:24 forKey:@"age"];
[userPrefs setBool:YES forKey:@"isLogin"];
 
// remove
[userPrefs removeObjectForKey:@"debts"];
 
[userPrefs synchronize];  // 将修改过的值写入磁盘,未修改过的值则从磁盘中进行同步
 
// get
NSString *userID = [userPrefs stringForKey:@"userID"];
BOOL isLogin = [userPrefs boolForKey:@"isLogin"];

NSCoding

SQLite

Core Data

多线程

NSThread

NSOperation / NSOperationQueue

GCD

破解

开源库/控件

LibComponentLogging

  • 日志库,基于宏接口的一套库,可以配接不同的 back-end,比如写文件,输出到 NSLog 或者把 NSLogger 作为 back-end

NSLogger

CocoaLumberjack

CrashKit

QuickDialog

MGSplitViewController

SVPullToRefresh

EGOTableViewPullRefresh

BButton

UIBubbleTableView

PKRevealController

ZBarSDK

Cocoa-Touch-Barcodes

QR-Code-Encoder-for-Objective-C

AFNetworking

数据获取及编码转换

当调用 AFHTTPClient 的如下方法时:

- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request 
                                                    success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                                                    failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;

成功时,responseObject 是一个 NSData 类型的数据块,这个 NSData 数据块可以通过如下方式转换成 NSString 来进行操作:

NSString *data = [[NSString alloc] initWithData:responseObject
                                       encoding:NSUTF8StringEncoding];

但是 NSData 的字符编码不一定都是 UTF-8,如果是其它编码的话,转换后得到的 NSString 会是空的,如下例:

Content-Type: text/html; charset=ISO-8859-1

其实 operation 里面包含有 HTTP Server 的 response - NSHTTPURLResponse *response; [operation.response allHeaderFields] 可以得到 NSDictionary * 类型的全部回应头。

而如下 code 可以始终让 NSData 按正确的编码转换到 NSString:

CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding( (CFStringRef)[operation.response textEncodingName] );
NSStringEncoding encoding = CFStringConvertEncodingToNSStringEncoding( cfEncoding );
NSString *data = [[NSString alloc] initWithData:responseObject encoding:encoding];

执行绪

默认情况下,AFNetworking 的 success 和 failure block 是运行在主(UI)线程里的:

NSURL *url = [NSURL URLWithString:@“http://www.google.com”];
AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:url];
[client setDefaultHeader:@"User-Agent" value:@"foobar"];
 
NSMutableURLRequest *request = [client requestWithMethod:@"GET" path:@"/" parameters:nil];
 
AFHTTPRequestOperation *operation =
	[client HTTPRequestOperationWithRequest:request
                                        success:^( AFHTTPRequestOperation *operation, id responseObject )
{
	// in main thread
}
                                        failure:^( AFHTTPRequestOperation *operation, NSError *error )
{
	// in main thread
}];

但是也可以改变:

AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.successCallbackQueue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 );
operation.failureCallbackQueue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 );
[operation setCompletionBlockWithSuccess:^( AFHTTPRequestOperation *operation, id responseObject )
{
	// in global thread
}
                                 failure:^( AFHTTPRequestOperation *operation, NSError *error )
{
	// in global thread
}];

我的封装

static NSString *gUserAgent = @"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6";
 
+ (void)doHttpGetAt:(NSString *)baseUrl
               path:(NSString *)path
	  successInMain:(BOOL)successInMain
                success:(void (^)(NSString *))success
	  failureInMain:(BOOL)failureInMain
                failure:(void (^)(NSError *))failure
{
	NSURL *url = [NSURL URLWithString:baseUrl];
	AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:url];
	[client setDefaultHeader:@"User-Agent" value:gUserAgent];
 
	NSMutableURLRequest *request = [client requestWithMethod:@"GET"
														path:path
												  parameters:nil];
 
	AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
	if( !successInMain )
		operation.successCallbackQueue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 );
	if( !failureInMain )
		operation.failureCallbackQueue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 );
	[operation setCompletionBlockWithSuccess:^( AFHTTPRequestOperation *operation, id responseObject )
	{
		NSString *data = [operation responseString];
		success( data );
	}
									 failure:^( AFHTTPRequestOperation *operation, NSError *error )
	{
		failure( error );
	}];
 
	[operation start];
}

MKNetworkKit

FSNetworking

SVProgressHUD

Three20

FMDB

CMPopTipView

ATMHud

ShareKit

  • 一个可以在 app 中非常简单的集成 share 功能的强大库
  • http://www.getsharekit.com/ - 此处的 code 不再更新
  • https://github.com/ShareKit/ShareKit - 最新代码!注意安装指引中的头文件引用路径 Submodules 目录是指把整个 ShareKit 目录放在工程目录中的 Submodules 里面,如果目录结构不是如此的话,会发生找不到 SHK.h 的编译错误,而且这个目录的位置相对于项目的 .xcodeproj 文件的。
  • framework
    • SystemConfiguration.framework
    • Security.framework
    • MessageUI.framework
    • CFNetwork.framework (for Flickr)
    • CoreLocation.framework (for Foursquare)
    • Twitter.framework (it is new in iOS 5, so if you deploy to older versions of iOS, mark it optional)
    • CoreFoundation.framework (mark it optional, it is a workaround for issue #394)
    • if you plan to use print sharer, and have deployment target < iOS 4.2 you have to mark UIKit.framework optional too, unfortunately.

StackScrollView

JASidePanels

RaisedCenterTabBar

JSONKit

RegexKitLite

NSString+HTML.m

Godzippa

TSMiniWebBrowser

UITabBarController-iAds

enormego @ Github

vCard

ios_notes.txt · 最后更改: 2013/10/16 08:45 由 2ndboy