How to Deploy iOS Applications into Any Device out of Apple Store – Using dropbox

This is the previous article about how to do this on self managed host.
But we can take the advantage of Dropbox to do this, will be more convenient.

  1. Upload the signed ipa file to dropbox, and get the link of the ipa. It will be something like:
https://www.dropbox.com/s/xdfycqlw4e1r6bt/someapp.ipa?dl=0

Replace www.dropbox.com to dl.dropboxusercontent.com to modify it as:

https://dl.dropboxusercontent.com/s/xdfycqlw4e1r6bt/someapp.ipa?dl=0
  1. Create manifest file as:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>items</key>
    <array>
        <dict>
            <key>assets</key>
            <array>
                <dict>
                    <key>kind</key>
                    <string>software-package</string>
                    <key>url</key>
                    <string>https://dl.dropboxusercontent.com/s/xdfycqlw4e1r6bt/someapp.ipa?dl=0</string>
                </dict>
                <dict>
                    <key>kind</key>
                    <string>display-image</string>
                    <key>url</key>
                    <string>https://dl.dropboxusercontent.com/s/0ef4etggy0aw8sa/image.png</string>
                </dict>
                <dict>
                    <key>kind</key>
                    <string>full-size-image</string>
                    <key>url</key>
                    <string>https://dl.dropboxusercontent.com/s/0ef4etggy0aw8sa/image.png</string>
                </dict>
            </array>
            <key>metadata</key>
            <dict>
                <key>bundle-identifier</key>
                <string>YOUR APP BUNDLE IDENTIFIER</string>
                <key>bundle-version</key>
                <string>1.0</string>
                <key>kind</key>
                <string>software</string>
                <key>title</key>
                <string>YOUR APP NAME</string>
            </dict>
        </dict>
    </array>
</dict>
</plist>

Please replace the ipa path, image path and bundle identifier, app name in this file.
The ipa path is what got from step 1.
And update this manifest file to Dropbox. And similar to ipa file, get the manifest file link from something like:

https://www.dropbox.com/s/imqvkwe2y395aua/manifest.plist?dl=0

To

https://dl.dropboxusercontent.com/s/imqvkwe2y395aua/manifest.plist

Please notice the postfix ?dl=0 also removed.

  1. Create a install html page using this manifest link:
<h1>Install</h1>
<a href="itms-services://?action=download-manifest&url=https://dl.dropboxusercontent.com/s/imqvkwe2y395aua/manifest.plist">
  Install YOUR APP NAME
</a>

Please replace the manifest link and YOUR APP NAME.


Then open the install.html in Safari and enjoy!

Distribution Items Equally in View with Auto-Layout in iOS

Taking this layout as example, we want to place 2 buttons in the view, and need them distributed in the view equally in the view.
Screen Shot 2016-02-27 at 21.57.16



So there are 3 spaces:
1. from superview left to center of “Previous” button;
2. from center of “Previous” button to center of “Next” button;
3. from center of “Next” button to right edge of superview.



If those 3 spaces are equal, then we achieved the goal.
It’s simple to do this with automatic layout in iOS:
Create 2 constrains:
Screen Shot 2016-02-27 at 21.58.19
1. from “Previous” button center to tailing of superview, with constant 0 and multiplier 3:1;
2. from “Next” button center to tailing of superview, with constant 0 and multiplier 3:2.



All set, enjoy!

Link Error of undefined symbol delete[] and new[] in iOS Development with WXAPI

和微信Api对接的时候遇到这个问题:

Undefined symbols for architecture x86_64:
  "operator delete[](void*)", referenced from:
      +[WeChatApiUtil EncodeBase64:] in libWeChatSDK.a(WeChatApiUtil.o)
      +[WeChatApiUtil NsDataEncodeBase64:] in libWeChatSDK.a(WeChatApiUtil.o)
      +[WeChatApiUtil DecodeWithBase64:] in libWeChatSDK.a(WeChatApiUtil.o)
      +[WeChatApiUtil DecodeBase64:] in libWeChatSDK.a(WeChatApiUtil.o)
  "operator new[](unsigned long)", referenced from:
      +[WeChatApiUtil EncodeBase64:] in libWeChatSDK.a(WeChatApiUtil.o)
      +[WeChatApiUtil NsDataEncodeBase64:] in libWeChatSDK.a(WeChatApiUtil.o)
      +[WeChatApiUtil DecodeWithBase64:] in libWeChatSDK.a(WeChatApiUtil.o)
      +[WeChatApiUtil DecodeBase64:] in libWeChatSDK.a(WeChatApiUtil.o)
  "___gxx_personality_v0", referenced from:
      Dwarf Exception Unwind Info (__eh_frame) in AppDelegate.o
      Dwarf Exception Unwind Info (__eh_frame) in main.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

解决方法就是添加libc++.dylib

How to save image data with exif information from UIImagePickerController

The UIImage returned from UIImagePickerController is without exif information, if we want generate an NSData from it, saving to disk or sharing via network for example. We can mix the exif information into the NSData.

Like this:

- (NSData *)dataFromImage:(UIImage *)image metadata:(NSDictionary *)metadata mimetype:(NSString *)mimetype
{
    NSMutableData *imageData = [NSMutableData data];
    CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef)mimetype, NULL);
    CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, uti, 1, NULL);

    if (imageDestination == NULL)
    {
        NSLog(@"Failed to create image destination");
        imageData = nil;
    }
    else
    {
        CGImageDestinationAddImage(imageDestination, image.CGImage, (__bridge CFDictionaryRef)metadata);

        if (CGImageDestinationFinalize(imageDestination) == NO)
        {
            NSLog(@"Failed to finalise");
            imageData = nil;
        }
        CFRelease(imageDestination);
    }

    CFRelease(uti);

    return imageData;
}

and in the callback of UIImagePickerController, do this:

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
    NSDictionary *metadata = [info objectForKey:UIImagePickerControllerMediaMetadata];
    NSString *mimeType = @"image/jpeg"; // ideally this would be dynamically set. Not defaulted to a jpeg!

    // you could add to the metadata dictionary (after converting it to a mutable copy) if you needed to.

    // convert to NSData
    NSData *imageData = [self dataFromImage:image metadata:metadata mimetype:mimeType];

    // ...
}

Referrence
http://stackoverflow.com/questions/15239719/convert-uiimage-to-nsdata-without-using-uiimagepngrepresentation-or-uiimagejpegr

Push Notification, Step by Step Setup.

坑爹的苹果,居然在提交证书的时候,由于浏览器(自家的Safari)的问题导致提交不成功,换成别的浏览器才成功。
The device token for Development and for Production is DIFFERENT!

(!!! DO NOT use the Safari, When access the develop.apple.com)
1.Access the certificate for push notification
1.获取用于推送通知的证书

1.1.Request 2 certificate from the KeyChain Assistant (CSR), one for develop, one for production. If you do not know how to do it, see Instruction 1.4 below. And export the associated Private Key as .p12 files. So after this step, we get 4 files.
1.1.从证书助手获取两个证书,一个在开发中使用,一个在发布的产品中使用(详见1.4中的系统提示)

1.2.On developer.apple.com in the Provisioning Portal, Select the App Id that you want to enable Push Notification, and click the ‘Config’ button.
1.2.在developer.apple.com的Provisioning Portal中,选择要发送推送通知的App Id,点击“Config”按钮

1.3.Check the Enable Push Notification Check box
1.3.勾上Enable Push Notification多选框

1.4.Click the ‘Config’ button for develop, and there is a guide for ‘How to request a certificate from KeyChain Assistant’. When you’ve done, click the ‘Continue’.
1.4.点击开发对应的’Config’,然后回出来一个向导告诉你如何生成CSR文件

1.5.Select the CSR file that generated by last step, then click ‘Generate’. (In this step, DO NOT use the Safari, or the ‘generate’ button will not response to your click at all……)
1.5.在最后一步,选择生成的CSR文件,然后点击‘Generate’。(坑爹的就是Safari下这个按钮点了没有任何效果,换别的浏览器就好了,汗···)

1.6.Download the generated certificate and install on your server.(On mac, just double click the certificate)
1.6.下载生成的证书并且双击它进行安装

1.7.Do the same for Production, following step 1.4,1.5,1.6.
1.7.对于发布版本,按照1.4, 1.5 和1.6的步骤再来一次。

2.Build the .pem file for our server to connect to APNS.
2.生成用来从服务器连接APNS的PEM文件

2.1.Copy the .p12, .csr, .cer files together in one place.
2.1.将前面得到的.p12, .csr, .cer文件放在一起。这时一共有6个文件,对应Develop,比如叫做dev.p12,dev.csr,dev.cer;对于发布有另外三个文件,这里以dev为例

2.2.Convert the dev.cer into devCer.pem. In the terminal, run

openssl x509 -in dev.cer -inform der -out devCer.pem

2.2.把dev.cer转换成devCer.pem。打开终端,运行

openssl x509 -in dev.cer -inform der -out devCer.pem

2.3.Conver the dev.p12 into devKey.pem. In the terminal, run openssl pkcs12 -nocerts -out devKey.pem -in dev.p12. First you will be asked to input the password for the dev.p12 file, and then will let you input your password for devKey.pem file.
2.3.把dev.p12转换成devKey.pem. 在终端中运行 openssl pkcs12 -nocerts -out devKey.pem -in dev.p12。中间会让你首先输入.p12文件的密码,然后让你输入密码来保护生成的.pem文件

2.4.Combine the two pem file together. In the terminal, run

cat devCer.pem devKey.pem > dev.pem

2.4.把两个pem文件合成一个。运行

cat devCer.pem devKey.pem > dev.pem

2.5.Then we can test if things goes well. By run

telnet gateway.sandbox.push.apple.com 2195

to see if you can reach the APNS. Then run

openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert devCer.pem -key devKey.pem

to see if the two pem files are ok.
2.5.然后我们可以测试一下是否一切都正常。先运行

telnet gateway.sandbox.push.apple.com 2195

确定可以连接到APNS,然后运行

openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert devCer.pem -key devKey.pem

看看pem文件是不是ok的。

3.Setup the application project in xCode.
3.在xCode中配置应用程序

3.1.Open the application project in xCode, and code sign with the specific (the exact same bundle id) provisioning file. If you want debug the push notification in xCode, be sure the right develop provisioning file is used.
3.1.在code sign部分,选择正确的provisioning file

3.2.Add some code to enable push notification in your application as:
3.2.在app delegate中添加以下代码:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
    //Some other code here ... for the window or something.
    return YES;
}
#pragma mark notification
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
    NSLog(@"Failed to Get Token, Error: %@", error);
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
    NSLog(@"Success Get Token : %@", deviceToken);
}

3.3.Run your application in the real device, not simulator(the simulator is not support the push notification)
3.3.在真机上运行应用程序(模拟器是不支持推送的,也无法测试)

3.4.If you get the log of “Failed to Get Token”, then you must have something wrong in the previous steps.
3.4.如果发现输出了”Failed to Get Token”,说明前面步骤上有问题。

3.5.Else you will get a log like : “Success Get Token : <90757852 {6 segment of this kind of hex numbers} 82728ad7>“, record this deviceToken, which will be used for testing.
3.5.正常情况下,你可以获得一条消息”Success Get Token : <90757852 {6 segment of this kind of hex numbers} 82728ad7>“,记录下这段deviceToken,后面的测试会用到它

3.6.On your device, there will be an alert to ask permission for push notification, tap on ‘OK’
3.6.在第一次运行的时候,应该会有系统的alert让你允许推送通知,点’OK’就行。

4.Test the Push Notification For Development
4.然后就可以测试一下推送了。

4.1.paste the following code in to testPush.php file
4.1.把下面这段代码保存为testPush.php文件

 $message,
    'sound' => 'default'
    );

// Encode the payload as JSON
$payload = json_encode($body);

// Build the binary notification
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;

// Send it to the server
$result = fwrite($fp, $msg, strlen($msg));

if (!$result)
    echo 'Message not delivered' . PHP_EOL;
else
    echo 'Message successfully delivered' . PHP_EOL;

// Close the connection to the server
fclose($fp);

4.2.Modify the variables at the first few lines according to your situation. The deviceToken will be used here.
4.2.修改文件最开始的几个变量。这里会用到你刚才记录下的deviceToken。

4.3.Running the php code.
4.3.运行一下这段php代码。

php testPush.php

Reference:
http://www.raywenderlich.com/3443/apple-push-notification-services-tutorial-part-12
http://article.ityran.com/archives/194
http://www.cocoachina.com/applenews/devnews/2012/1212/5312.html

【转】iOS开发中的UITableView编辑模式

文章写得很好,转载自:http://my.oschina.net/plumsoft/blog/53271

这篇文章主要讲的表格的操作包括:标记行、移动行、删除行、插入行。

这次就不从头建立工程了,在http://www.oschina.net/code/snippet_164134_9876下载工程。这个工程就是最简单的产生一个表格并向其中写入数据。用Xcode 4.2打开它,在这个工程基础上实现以上操作。

1、标记行

这里讲的标记行指的是单击此行,可以实现在此行右边出现一个勾,如下图所示:

为了实现标记功能,在ViewController.m中@end之前添加代码:

#pragma mark -
#pragma mark Table Delegate Methods
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 
    UITableViewCell *oneCell = [tableView cellForRowAtIndexPath: indexPath];
    if (oneCell.accessoryType == UITableViewCellAccessoryNone) {
        oneCell.accessoryType = UITableViewCellAccessoryCheckmark;
    } else 
        oneCell.accessoryType = UITableViewCellAccessoryNone;
    [tableView deselectRowAtIndexPath:indexPath animated:YES]; 
}

该代码实现:单击某行时,若此行未被标记,则标记此行;若此行已经被标记,则取消标记。

运行效果如上图。

上面的代码实际上就是修改某行的accessoryType属性,这个属性可以设为四个常量:

UITableViewCellAccessoryCheckmark
UITableViewCellAccessoryDetailDisclosureButton
UITableViewCellAccessoryDisclosureIndicator
UITableViewCellAccessoryNone

效果依次如下图所示:

            

   UITableViewCellAccessoryCheckmark            UITableViewCellAccessoryDetailDisclosureButton

                

UITableViewCellAccessoryDisclosureIndicator                   UITableViewCellAccessoryNone

注意,上面第二张图片中的蓝色圆圈不仅仅是一个图标,还是一个控件,点击它可以触发事件,在上一篇博客《iOS开发16:使用Navigation Controller切换视图》使用过。

2、移动行

想要实现移动或者删除行这样的操作,需要启动表格的编辑模式。使用的是setEditing:animated:方法。

2.1 打开ViewController.xib,将其中的表格控件映射成Outlet到ViewController.h,名称为myTableView。

2.2 打开ViewController.m,在viewDidLoad方法最后添加代码:

//启动表格的编辑模式
[self.myTableView setEditing:YES animated:YES];

2.3 在@end之前添加代码:

//打开编辑模式后,默认情况下每行左边会出现红的删除按钮,这个方法就是关闭这些按钮的
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView
           editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath { 
    return UITableViewCellEditingStyleNone; 
} 

//这个方法用来告诉表格 这一行是否可以移动
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { 
    return YES; 
}

//这个方法就是执行移动操作的
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)
        sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
    NSUInteger fromRow = [sourceIndexPath row]; 
    NSUInteger toRow = [destinationIndexPath row]; 
    
    id object = [list objectAtIndex:fromRow]; 
    [list removeObjectAtIndex:fromRow]; 
    [list insertObject:object atIndex:toRow]; 
}

editingStyleForRowAtIndexPath这个方法中用到了常量UITableViewCellEditingStyleNone,它表示不可编辑,这里的编辑指的是删除和插入。表示表格行的编辑模式的常量有:

UITableViewCellEditingStyleDelete
UITableViewCellEditingStyleInsert
UITableViewCellEditingStyleNone

顾名思义,第一个表示删除,第二个表示插入,第三个表示不可编辑。

若将editingStyleForRowAtIndexPath方法中的UITableViewCellEditingStyleNone依次换成上面三个值,则它们运行的效果依次如下图所示:

     

2.4 运行,从下图可以看到实现了行的移动:

但是也会发现,现在无法对每行进行标记了。这说明,在编辑模式下,无法选择行,从而didSelectRowAtIndexPath这个方法不会执行。

3、删除行

从第2步过来,实现删除某行,其实比较简单了。

3.1将editingStyleForRowAtIndexPath方法中的UITableViewCellEditingStyleNone修改成UITableViewCellEditingStyleDelete。

3.2 在@end之前添加代码:

//这个方法根据参数editingStyle是UITableViewCellEditingStyleDelete
//还是UITableViewCellEditingStyleDelete执行删除或者插入
- (void)tableView:(UITableView *)tableView commitEditingStyle:
    (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    NSUInteger row = [indexPath row];
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        [self.list removeObjectAtIndex:row]; 
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                         withRowAnimation:UITableViewRowAnimationAutomatic]; 
    }
}

在这个方法中又出现了一个常量:UITableViewRowAnimationAutomatic,它表示删除时的效果,类似的常量还有:

UITableViewRowAnimationAutomatic
UITableViewRowAnimationTop
UITableViewRowAnimationBottom
UITableViewRowAnimationLeft
UITableViewRowAnimationRight
UITableViewRowAnimationMiddle
UITableViewRowAnimationFade
UITableViewRowAnimationNone

它们的效果就不一一介绍了,可以在实际使用时试试。

3.3 运行,看看效果:

     

刚运行时显示如左边的图片,点击某一行左边的圆圈图标,会显示如中间图片所示。然后点击Delegate按钮,那一行就会被删除掉,如右边的那张图片所示,它显示的是删除时的效果。

4、插入行

这个与删除行类似。

4.1 首先将editingStyleForRowAtIndexPath方法中的UITableViewCellEditingStyleDelete修改成UITableViewCellEditingStyleInsert。

4.2在3.2添加的方法中添加代码:

else {
    //我们实现的是在所选行的位置插入一行,因此直接使用了参数indexPath
    NSArray *insertIndexPaths = [NSArray arrayWithObjects:indexPath,nil];
    //同样,将数据加到list中,用的row
    [self.list insertObject:@"新添加的行" atIndex:row];
    [tableView insertRowsAtIndexPaths:insertIndexPaths withRowAnimation:UITableViewRowAnimationRight];
}

上面的代码中也可以不用insertRowsAtIndexPaths方法,而直接使用[tableView reloadData];语句,但是这样就没有添加的效果了。

4.3 好了,运行一下:

     

刚运行时如上面左图所示,单击了某个加号后,新的一行就从右边飞进来了,因为在insertRowsAtIndexPaths中用了参数UITableViewRowAnimationRight。

 

ARC – Automatic Reference Counting in iOS Development

ARC帮我们做了很多事情,主要的几点如下:

1·自动生成dealloc函数,帮我们释放对象中的变量
2·如果你要自己写dealloc,它会帮你加上[super dealloc];
3·AutoreleasePool用@autoreleasepool{}的block代替
4·weak修饰符,在所指对象被释放后自动置为nil,cool!



参考(Ref.)
https://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html

Why we use ‘copy’ attribute for NSString property?

@interface AClass:NSObjet
@property (nonatomic, copy) NSString* aStrProperty;
@end

Like the code above, we use ‘copy’ instead of ‘retain’ attribute in the NSString property. For the reason, is that we do not want the original NSString object to be modified in this class.
像上面这段代码,为什么要用copy而不是retain呢?因为我们不希望外面的字符串在不经意间由这个类的对象修改。

But for that a NSString cannot be modified, why not using the ‘retain’ instead of ‘copy’? Because, one can also assign the aStrProperty with a NSMutableString instead of NSString. Thus, ‘copy’ is more safe than ‘retain’.
可是,既然NSString是不可以被修改的,为什么还有这个担忧呢?因为虽然NSString不可修改,可是使用者完全可能将一个NSMutableString传递给aStrProperty,然后在类方法里修改它的内容,这不是我们希望出现的,所以采用copy。

Size with UITextView in iOS Development

In the iOS development, the UITextView is frequently used for display a NSString with multiple lines.
(在iOS开发中,经常用UITextView来显示一段多行的NSString)

In some of the applications, we need place a UITextView in UITableViewCell.
(在有些时候,会在UITableCell中使用它)

In the UITableViewCell, the height of cell is determined according to the height of UITextView, and the height of UITextView is dependent to the size of NSString which is displaying.
(然后如果要根据字符串绘制之后所占的高度来动态调整UITableViewCell的高度的话,就需要去计算字符串所占大小,再用这个大小去计算UITextView所占大小,再进一步计算Cell的高度)

When we calculated the size of NSString with the methods -sizeWithFont…blablabla
(用NSString自带的-sizeWithFont….系列函数可以计算出字符串的绘制所占用的区域大小)

It’s just the size of text.
(但是这只是文本本身的大小)

Outside the text, there is some space edge within the UITextView, and the edge is 8px in the top and bottom direction, 11px in the left and right direction.
(UITextView还有一个Padding边框,这个边框的大小是top和bottom是8px,left和right是11px)

So, we can get the height of text first, and then add 16px to get the height of UITextView, and then calculate the height of the UITableViewCell.
(所以,我们可以先计算出文本的高度,然后加上16px得到UITextView的高度,再去计算Cell的高度)

【转】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的效率会稍低一点,所以只有当你非常需要它提供的可扩展性时才使用它。

Toll Free Bridge in iOS Development

Toll Free Bridge is the channel connecting the Cocoa and CoreFoundation.
TFB是连接Cocoa和CoreFoundation的一个设计。
For example, we can use an Objc object of NSString as CFString, or reversed.
我们可以在程序中把NSString当做CFString来用,也可以把CFString当做NSString来用。
The detail is described in the article below.
下面这篇文章很详细得介绍了它。
http://www.winddisk.com/2012/08/16/toll-free-bridging/

Remove Push Notifications from Notification Center in iOS Development

If the notification is local, it’s easy by
如果是本地通知,下面的代码就解决了。

[[UIApplication sharedApplication] cancelAllLocalNotifications];

But if the notifications come from the push notification, this does’t work.
但是如果是来自于推送的话,这不管用。
After search and try, only the third method in a blog works, although it’s weird.
搜了很久发现有一个办法可以解决,虽然这个办法非常怪异。

UIApplication* application = [UIApplication sharedApplication];
NSArray* scheduledNotifications = [NSArray arrayWithArray:application.scheduledLocalNotifications];
application.scheduledLocalNotifications = scheduledNotifications;

<br>
Reference
http://josh-asch.net/2012/02/29/ios-development-remove-old-notifications-from-notification-center/

How to Deploy iOS Applications into Any Device out of Apple Store

Pre-Conditions:
You have an apple developer account.

The first way, is that you plug in the device into the Mac, on which your application is developed and let xcode to install the applications automatically.

But, if you cannot access the device directly, this is not the way that fulfilled.

However, in this case, we can deploy an iOS application into Devices by Ad-Hoc and thus do not wait for the apple store review. Just following the steps below:

  1. Login into developer.apple.com, in the Devices tab, add the target device by it’s UDID
  2. In the provisioning tab, open the Distribute tab, add an Ad-Hoc, provisioning profile with the target device selected
  3. Download this provisioning file and open it, let it available in the Xcode
  4. Open the project in Xcode, using the provisioning profile downloaded in the code sign for release option
  5. Archive the project again.
  6. In the organizer opened automatically after archive is done, select the archived item and press the button ‘Distribute’
  7. Select Ad-Hoc, and Next
  8. Select ‘Enterprise’, fill in the url with the ipa url,such as”http://somedomain.com/appname.ipa
  9. And save the file exact as the name that specified in the url you typed above
  10. Upload the two files generated above, one plist, one ipa, onto your web site,and located exactly as in the url you specified above.
  11. Make a url link on your website like”itms-services://?action=download-manifest&url=http://somedomain.com/appname.plist
  12. Done!

After doing the above steps, you can let the target device to open your web page with Safari, and press the link. Then the device can download and install the application. And any device with jailbreak can install as well, even if it does not exist in your Devices list.

But after iOS 7, the ad-hoc deploy must with Https instead of Http
So we need to modify the url link to something like:
itms-services://?action=download-manifest&url=https://somedomain.com/appname.plist
And the host need to be configured with ssh access, for an apache server, you can do this with the steps in this blog.
And after you configured your server well, you need one more step, that copy your certificate generated in this blog to a public place, and use your iOS device open it with Safari, so this certificate can be installed in your device. After this, the app can be installed as well as the old days.

iOS开发和服务器通讯遇到的编码问题

今天遇到一个bug,是服务器那边传过来的用UTF8编码之后的Json字符串,解UTF8之后,Json解析失败。

然后打印源字符串,其中会遇到很多的\200\213\123这样的内容导致解析失败。

其实这是假象。

在突发奇想将每个遇到的\替换成\\之后,问题解决了,解决了了···

但是再后来,发现这个做法有点过,就是其实还有一些正常的\不应该被变成\\,于是想到用正则表达式去匹配和替换。

用前向断言正则匹配\\(?=\d{3})去匹配,发现没有一个被匹配出来,而眼睁睁的看着打出的log里面确实是有\ddd\ddd\ddd这样的内容的,也确实就是这样的内容导致了json解析失败。

然后百思不得其解,千思也不得其解。终于忍了,自己写了一个字符串匹配替换,才发现问题的根源不在与\ddd\ddd\ddd,而在于\&,在debug窗口打出来的log和源字符串是不一样的····完全没有一个斜杠跟着3个数字的问题,出现的只有两种斜杠,\n\&。。。

后来吧\&替换成\\&就ok了。

iOS开发中的UISearchBar背景替换,自定义UISearchBar背景

UISearchBar的背景在5.0之前并没有提供SDK来替换。

但是实际应用中又常常需要替换掉默认的背景,于是要么自己重写一个searchbar要么寻求别的方法。好在于在SDK5.0之后,提供了一个简单的方法来做:

@property(nonatomic,retain) UIImage *backgroundImage NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR;

在5.0之前怎么办呢?可以用下面的办法(未经测试,目测靠谱):

    for (UIView *sub in self.searchBar.subviews) {
        if ([sub isKindOfClass:NSClassFromString(@"UISearchBarBackground")]) {
            //this is the bkg view, hide it and insert another view
            UIImageView *newbkg = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"newbkg"]];
            newbkg.frame = sub.frame;
            [self.searchBar insertSubview:newbkg belowSubview:sub];
            [newbkg release];
            sub.hidden = YES;
            break;
        }
    }

解决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];
}

iOS开发中的NSDate时区问题

iOS设备上获取的时间,呵呵,不好说,有时候是GMT时间(比如[NSDate date]),有时候是本地时间,比如用位置传感器传回来的时间戳。而且现在的应用国际化了,还是要考虑时区问题。

在iOS中,处理时区问题是用NSTimeZone来进行的,下面是一个简单的例子。

-(void)testTime{
    NSDate *now = [NSDate date];//根据当前系统的时区产生当前的时间,绝对时间,所以同为中午12点,不同的时区,这个时间是不同的。
    NSDateFormatter *df = [[NSDateFormatter alloc] init];
    df.timeZone = [NSTimeZone systemTimeZone];//系统所在时区
    df.dateFormat = @"yyyy-MM-dd HH:mm:ss";
    NSString *systemTimeZoneStr =  [df stringFromDate:now];
    df.timeZone = [NSTimeZone defaultTimeZone];//默认时区,貌似和上一个没什么区别
    NSString *defaultTimeZoneStr = [df stringFromDate:now];
    df.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:8 * 3600];//直接指定时区
    NSString *plus8TZStr = [df stringFromDate:now];
    df.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];//这就是GMT+0时区了
    NSString *gmtTZStr = [df stringFromDate: now];
    NSLog(@"Test Time\nSys:%@\nDefault:%@\n+8:%@\nGMT:%@",systemTimeZoneStr,defaultTimeZoneStr,plus8TZStr,gmtTZStr);
}

该函数运行的结果是:(在+8区,当地时间19:06:07运行)

Test Time
Sys:2013-06-30 19:06:07
Default:2013-06-30 19:06:07
+8:2013-06-30 19:06:07
GMT:2013-06-30 11:06:07

利用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项目中使用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];

NSString和NSDate相互转换

使用方法如下所示:

NSString *dateStr = @"20120821161441";
NSDateFormatter *formater = [[NSDateFormatter alloc] init];
[formater setDateFormat:@"yyyyMMddhhmmss"];
NSDate *date = [formater dateFromString:dateStr];
[formater setDateFormat:@"yyyy-MM-dd hh:mm:ss"];
dateStr = [formater stringFromDate:date];
//dateStr is :2012-08-21 16:14:41

格式化参数如下:

G: 公元时代,例如AD公元
yy: 年的后2位
yyyy: 完整年
MM: 月,显示为1-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: 毫秒
Z: GMT时间

Examples

//like 2016-01-18T20:35:11.011Z
"yyyy-MM-dd'T'HH:mm:ss.SSSZ"

UIImagePickerController的自定义,状态栏空白问题

UIImagePickerController提供了很方便的界面和逻辑让我们可以直接拍照或者从相机胶卷选取照片。

使用的方法如下所示:

但是使用过程中也遇到一些问题,比如:
1.如何自定义拍照界面:

2.当[ctrl dismiss]之后,有时会出现20像素的下移空白,其实这个很容易解决。
比如,假设imagepicker是从parantController弹出来的,那么只需要在parentController的viewWillAppear函数中加入调整view.frame的代码即可。

3.设置界面语言,如设置为中文
默认是是英文的,如果需要中文的话,需要设置多语言环境,其实只需要创建一个.strings文件即可。

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