// // //

大钟威武

从白痴到大师的点滴积累

更新xcode5.1后要注意的两点

| Comments

这几天手机更新了iOS7.1,xcode也更新到了5.1,之后就跳进了两个坑……

1.UITableviewCell在Xcode5.1中默认未勾选Clip Subviews

结果会导致靠改变Cell的高度,隐藏一些内容的时候,无法隐藏成功:

X-CellTallHeight

当switch turn off的时候,想得到:

X-CellShortHeight

结果得到:

X-CellShortHeightUnexpected

解决办法,就是勾选上Cell的Clip Subviews。code实现这个功能的时候也需要注意:set clipsToBounds = YES

为什么Xcode5.1中默认改为未勾选呢?在Xcode 5.1 Release Notes中没提,所以,是为什么呢?欢迎指点

在《Xcode 5.1 Release Notes》中倒是提了下面这个“坑”:

2.默认Arm64编译

结果会导致不支持64位的部分都报错啦。

因为Xcode5.1把Arm64加入到了Standard architectures中,所以解决办法就是别使用“Standard architectures”。

可以在Targets—>Build Settings—>Architectures中,选择Other,然后增加“+”,录入进去armv7,再添加上armv7s,删除$(ARCH_STANDARD),如下图:

X-ModifyArch


看起来修改了这两个,就和原来没啥差别了。我的项目还没用到《Xcode 5.1 Release Notes》中提到的其他更新点。

iOS播放gif最棒的库,简单高效

| Comments

iOS 播放 gif最棒的库,简单高效。

Animated GIFs implemented the right way on iOS


iOS上播放gif,没有自带的API,所以我找了现成的库,借过来使用。github上我对比了3个star数目较多的代码,最终选择了OLImageView

参与对比的是OLImageView,iOS_AnimatedGifAnimated-GIF-iPhone

选择合适的库

对比的指标,我选择

1. 效率高,占用内存小

只播放同样一个Gif,OLImageView占用内存是后两者的4/5。

(这个测试可能不完备,没有测试同时播放多个gif,或者播放不同类型gif的对比。但对我使用来讲,只播放这个Gif,就可以说明问题了。)

2. 代码维护活跃

OLImageView近来都有更新。

后两个中,Animated-GIF-iPhone是从iOS_AnimatedGif分出来的,两个都已经分别3年多、1年多没有更新了,虽然去年iOS_AnimatedGif的作者在blog上说要做对ARC的支持,但也没看更新。

OLImageView用法

大家可以在github上,看到OLImageView的用法,用法是描述如何从code中创建和使用,非常简单直白。

我在这里介绍一下实际在Storyboard中使用的方式。

1. 文件拖入工程

下载的OLImageView文件共有6个,但只要把下面四个文件拖进你的工程即可:

OLImageViewFilesInProject

2. Storyboard中设置UIImageView为OLImageView

正常拖入一个UIImageView,在这个view的CustomClass中设定为OLImageView:

CustomClass

在Outlet中,设定为OLImageView类型

Outlet

3. code中操作

在合适的地方,把gif设定进去,注意这里的UIImage要换成OLImage:

1
    [self.guideImageGif1 setImage:[OLImage imageNamed:@"peaceful.gif"]];//注意这里的image要使用OLImage

我的gif是在scrolllView中播放,所以我还需要设定frame:

1
    [self.guideImageGif1 setFrame:self.myScrollView.frame];

效果

我在“药提醒你”的帮助VC中,背景是个gif,具体效果,你可以下载个下来看看^_^——右侧侧边栏“我的产品”。

最后

要是有时间,你尽量阅读一下库的代码。知其然也知其所以然。

我在这里也就写写用法,比较浅显。目的是能让有同样需求的同学,减少一点儿时间开销,哪怕只减少了几分钟,也值得了。

点击button实现Storyboard中TabBar Controller的tab切换

| Comments

环境和想要实现的功能

在Storyboard上,TabBarController作为rootViewController,此时想要在某个tab的VC中,点击个button,跳转到另外的tab上。如下图所示:

上图中,在第一个Tab上,点击“点击此处,去新建和管理提醒”,会跳转到第二个Tab,显示全部提醒的列表,来新建和管理提醒。

实现代码:

1
2
    AppDelegate *thisAppDelegate = [[UIApplication sharedApplication] delegate];
    [(UITabBarController *)thisAppDelegate.window.rootViewController setSelectedIndex:1];

分析: 我们在用代码创建app的时候,要在appDelegate中,去指定rootViewController。【不熟悉代码创建app的同学可以阅读这篇学习使用code实现iOS界面,在这篇blog中,推荐的IOS开发之纯代码界面—基本控件使用篇,非常适合新手学习code实现界面】在用Storyboard创建app的时候,虽然不用我们自己去指定rootViewController,但原理是一样的。

Storyboard中,app的入口箭头指向的VC,通常就是rootViewController。在这个例子中,就是UITabBarController。

通过

1
[[UIApplication sharedApplication] delegate]

得到自己这个appDelegate,通过调用

1
(UITabBarController *)thisAppDelegate.window.rootViewController

就得到了这个UITabBarController(的实例)。再使用UITabBarController(的实例)方法setSelectedIndex,去设定,要跳转到哪个Tab。

1
[(UITabBarController *)thisAppDelegate.window.rootViewController setSelectedIndex:1]

就是跳转到index为1的Tab,也就是第二个Tab了。

通过代码调用Storyboard中的scene —— Call Storyboard Scene Programmatically

| Comments

通过Storyboard搭建app框架,以及设计和实现一些view controller和view,是非常方便和高效的。

有时,同样一个scene(i.e. view controller),除了在Storyboard上通过segue达到以外,还需要在代码的某个地方,让它展现出来。

如何在代码中,调用一个已经在storyboard中设计好的scene呢?代码如下:

1
2
3
4
5
NSString * storyboardName = @"MainStoryboard_iPhone";
NSString * viewControllerID = @"ViewID";
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:nil];
MyViewController * controller = (MyViewController *)[storyboard instantiateViewControllerWithIdentifier:viewControllerID];
[self presentViewController:controller animated:YES completion:nil];

注:代码段来自Call storyboard scene programmatically (without needing segue)?

有两个注意的地方:
  1. 上面代码中的storyboardName不要包括”.storyboard”后缀。即,如果你的Storyboard文档叫做“Main.storyboard”,那么storyboardName应该叫@“Main”
  2. 注意:在Storyboard中,先给你的viewController加上ID,添加的地方如下图所示:在Indentity中的Storyboard ID。通过这个viewControllerID(图中的例子就是@“UserGuide”),在代码中找到这个vc。

storyboardID


关键字:代码中调用storyboard中的vc,代码中present storyboard scene

NSUserDefault 常用功能 检测应用(或app的某个版本)第一次运行 或 记录用户设定的属性”

| Comments

NSUserDefault,从名称也可看出,一般用来记录用户的设置的。这里介绍两种常用场景:检测应用(或app的某个版本)第一次运行记录用户设定的属性

原理

几句话说下我的理解:

  1. NSUserDefault使用方法standardUserDefaults得到全局的一个单例
  2. 在这个单例是个dictionary,即通过key-object来存、取信息
  3. 信息会存在plist文件中,你不删它,它就一直存在

检测app第一次运行

1
2
3
4
5
6
7
8
9
    // 以下这段代码,检查是否app的这个版本是否是第一次运行
    NSString *bundleVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey];
    NSString *appFirstStartOfVersionKey = [NSString stringWithFormat:@"first_start_%@", bundleVersion];
    NSNumber *alreadyStartedOnVersion = [[NSUserDefaults standardUserDefaults] objectForKey:appFirstStartOfVersionKey];
    if(!alreadyStartedOnVersion || [alreadyStartedOnVersion boolValue] == NO) {
        [self versionFirstStart];// app的bundleVersion这个版本第一次运行,你希望这时做点儿什么
        [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:YES] forKey:appFirstStartOfVersionKey];
    }
}

每次启动app时候,调用这段代码。检查一个叫做first_start_加版本号这么个key,对应的NSNumber类型Object,是否存在,或是否是0(NO),如果不存在,或者是0(NO),那么是这个版本的第一次运行,这时做你想在app第一次运行时做的事儿,比如[self versionFirstStart],之后在plist中添加这个key对应的NSNumber类型object,设置成1(YES)。这样,以后app只要未改变版本,启动时就再也不会执行[self versionFirstStart]了。

参见stackoverflow

P.S. 虽然我并未更改这段代码进行更多尝试,但肯定不一定非要NSNumber这个类型,只要plist能存储和读取的类型就可以。

记录用户设定的属性

想设定一个属性,你给这个属性起个独有的key,比如叫做@“your property key”。

设置属性:

1
[[NSUserDefaults standardUserDefaults] setObject:@"off" forKey:@"your property key"];

读取和判断属性:

1
2
3
if([[[NSUserDefaults standardUserDefaults] objectForKey:@"your property key"] isEqualToString:@"off"])
{
}

更多你要了解的NSUserDefault

1. 可存储的类型

NSUserDefault中存储的object的格式只能是以下列表中的类型,这是plist存储方式决定的。

1
2
3
4
5
6
7
array
dictionary
data
date
number - integer
number - floating point
Boolean

了解更多,可查阅Property List Programming Guide

2. 自动存储

使用NSUserDefault,默认是自动存储的,即你修改完之后,ios自动找个时候,同步(synchronize)一下内存和plist。

手动存储可直接调用synchronize方法:

1
[[NSUserDefaults standardUserDefaults] synchronize];
3. 查看目前NSUserDefaults standardUserDefaults中的内容

查看全部dict内容

1
NSLog(@"%@", [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]);

当然,也可以查看全部的key

1
NSLog(@"%@", [[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys]);

Outlook 转移OST数据文件 IMAP账户

| Comments

问题

windows8系统,装了Outlook2013,占用了C盘大约10G空间,主要都是数据文件(OST文件)占用的。希望能够把数据文件从C盘移至其他盘。并且账户是IMAP账户,不是Exchange。

google一下,绝大多数解决方案是针对以下两种情况:

  • 使用Exchange的账户,如何做到移动ost文件
  • 移动pst文件,而不是移动ost文件

都不适用。后来发现了这个解决方案,尝试后,成功!分享给大家:

解决方案

把ost文件移动到其他盘,在原来C盘的ost文件位置建立一个链接,链接到移动后的那个文件,大功告成。引用原作者的描述:

1
2
3
4
5
6
7
8
9
10
11
在Outlook2013 IMAP账户中

假设你目前的demo.pst文件在路径C:\Users\%username%\AppData\Local\Microsoft\Outlook下(已创建)

1. 关闭Outlook,移动此文件到D:\Outlook Files下。

2. 打开CMD,键入 mklink "C:\Users\%username%\AppData\Local\Microsoft\Outlook\XXXX.com.ost" "E:\Profile\Outlook\XXXX.com.ost"

3. 此时C:\Users\%username%\AppData\Local\Microsoft\Outlook下会有一个类似于快捷方式的同步文件demo.ost存在(0KB)。

4. 打开Outlook,IMAP账户仍然会挂接到C:\Users\%username%\AppData\Local\Microsoft\Outlook\demo.ost上,但该文件只是D盘下数据文件的映射,实际不消耗任何磁盘空间。

原文地址:Outlook2013 迁移OST存储位置

注意事项

使用的过程中有需要注意的地方:

1~ 需要使用管理员身份运行cmd

windows8的应用程序那儿,搜素cmd,出来“命令提示符”,右键点击,这时屏幕下方会出现几个选项,选择“”以管理员身份运行”

2~ 建立符号链接的位置的磁盘应该是NTFS格式

建立符号链接的位置,就是mklink后紧跟的参数的位置,如果不是NTFS格式的盘,恐怕就建立不了了。

P.S. 我更喜欢Mac

虽然windows是个很伟大的系统,并且其中的很多软件也异常优秀,但我已经很久不用windows了。日常使用,我觉得Mac系统是胜过windows的,尤其是对编程写代码的人。

但windows你也得熟悉,一方面由于别人的或公共的电脑windows占比很高,另一方面老婆向你求助的时候,你得帮忙解决问题啊,就像今天这个分享写的这事儿。

iOS ARC入门

| Comments

现在,iOS系统已经发展到iOS7,而iOS5时引入的ARC技术早已成为主流了。所以iOS新手们对ARC技术已经习以为常了吧,对之前的手工内存管理可能完全不了解,因为基本用不到。ARC又是如此简单,貌似也没有什么必须学习的。但了解一下ARC的原理原则还是必须的——因为了解技术的原理可以更好地对技术进行应用嘛。

本文算是阅读 Beginning ARC in iOS 5 Tutorial Part 1 (翻译在 iOS 5 ARC 入门 (1/3) 翻译的同学很有爱也很辛苦,但有些地方有错误,所以读原文会比较好。)

ARC简介

ARC,Automatic Reference Counting,在iOS5引入。

原理

简单说是代码在编译阶段,由编译器(LLVM 3.0)自动生成实例的引用计数管理的一些代码(插入retain/release等),起到内存管理的作用。

在ARC之前

在ARC之前,需要手工管理内存,原则是:

  • 如果你想保持一个对象可用,除非它已经被retain了,否则就需要retain它
  • 如果不再需要一个对象,就需要release它,除非它已经被release了(通过autorelease)

用ARC,程序会变慢吗?

不会!

ARC就是在需要retain和release的地方为你插入它们——这就是ARC和手工管理内存一样快的原因,当然有时ARC还会更快,因为它在后端还进行了一些优化操作。

ARC使用

这里只介绍个ARC使用方法的小子集,即仅记录了我觉得有意思的几个概念原则。ARC使用全集请看Beginning ARC in iOS 5 Tutorial Part 11

strong、weak

  • 有strong指针指向那个对象,那个对象就一直存在在内存中。这个原则对实例变量、属性、局部变量都使用
  • 默认所有实例变量局部变量等都是strong的指针,strong表示指针是变量的所有者
  • weak也可以指向一个对象,但不能是所有者
  • zeroing weak指针,是指weak指向的对象被释放了,weak指向的变量的值自动变为nil,这个特性防止了指向一个被释放的内存(例如悬空指针、僵尸等这样的说法这种问题就没有啦)
  • weak不常用,经常使用在父子对象上,因为父有strong指向子,子指向父的时候就只能用weak,常见的datasource、delegate都是这样

ARC特殊注意的地方

  • ARC不适用于Core Foundation 或 malloc() 和 free(),后者还是要手工管理内存
  • ARC 有效的时候,由于编译器帮我们做了内存管理的工作,所以我们不需要太担心。但是当与 ARC 管理以外的对象类型交互的时候,就需要特殊的转型关键字,来决定所有权的归属问题。比如“__bridge”。进一步了解可阅读参考文件2
  • 使用ARC,仍然要想着谁持有谁,后者的生命周期是怎么样的等,因为如果不释放指针,被持有者就一直在内存中

结语

易飞扬对ARC的7篇博文很值得推荐,大家想深入了解ARC原理可以去阅读: iPhone开发之深入浅出,注:要跳墙。

参考文献Beginning ARC in iOS 5 Tutorial Part 1中说:ARC是代表着OC的未来(大概因为ARC之前的内存管理是开发者们曾经的噩梦吧)。A smart developer tries to automate as much of his job as possible, and that’s exactly what ARC offers: automation of menial programming work that you had to do by hand previously. To me, switching is a no-brainer.

技术在不断飞速演进,做工程开发的我们,紧盯技术发展趋势,勇于接受新的东西。

老旧Mac如何免费获得iWork

| Comments

iWork免费了!以前仰望着的128元的产品,现在终于可以免费使用了!不过,据说需要买最新的硬件才能享受免费。于是就有了下面:老旧mac得到免费iWork的方式。

注:第二步中可能需要你有美国账号(其他国家的账号没试过,中国的不行),如果没有美国账号,据说更改系统的语言为英文也行。

第一步,装个09年的trial版

下载地址可以去这里

安装可能遇到的问题

无法open安装包

双击安装时,出现下面的对话框:

CannotOpen

解决方式

iOS7 Autolayout 瞬间入门!

| Comments

Autolayout是非常先进的一个技术。使用这种技术,适应不同设备屏幕大小差异或设备翻转时对界面的要求,变得很容易。这种技术提供了一种灵活的机制来描述界面上各控件的位置关系。

Xcode5使Autolayout技术更容易使用了。 之前,我对Autolayout只是听说有这种机制,但不知原理、用法。于是阅读了下面的两篇文章(作者: Matthijs Hollemans。目前貌似还没有翻译),算是在Storyboard/Xib上会用了。

以下作为阅读笔记,记录应该了解的使用Autolayout的要点(环境是Xcode5,iOS7SDK,Storyboard/Xib)。

原理要点

  • Autolayout基本是靠constraints来描述两个view之间的位置关系
  • Autolayout与以往的frame、bound、center包括autosizing mask等方式都不同,这是一个新的技术,使用Autolayout时候就不用考虑以上那些方式啦,不用再纠结这个view的位置是(x,y,width,height)了!
  • Autolayout描述位置关系的这两个view,或者是上下层关系,或者是同一层关系。即父子关系或都是父的子(兄弟关系^_^)
  • Constraints都是NSLayoutConstraint的对象,有一些属性可以在Attributes inspector中修改。当然也可以通过code的方式来实现Storyboard/Xib上的操作

使用方式

UITableViewCell在iOS7之后superview的问题

| Comments

在UITableView中针对某个Cell的操作

要针对某个UITableView中的Cell做些操作,比如对这个Cell的内容进行查看详细、编辑、删除、或者触发其他功能比如发短信、打电话等,删除、和点击这个Cell在UITableView的Delegate方法中提供,其他的操作就需要识别Cell上的控件(比如Button)对应的action是针对这个Cell的。

一种常用的方式是使用view的层次,在action中找到这个Cell:在

1
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

方法中,把这个Cell的Button加入到Cell的contentView的subView,并且为这个button添加Target。

1
2
3
// Button 操作
[cell.contentView addSubview:cell.button];
[cell.button addTarget:self action:@selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside];

之后在对应的

1
- (void)buttonAction:(id)sender

action中,通过view的层次,去找到对应的Cell:

1
2
3
4
5
6
7
8
// iOS7 之前
- (void)editRemindAction:(id)sender
{
    UIButton *button = (UIButton *)sender;
    UITableViewCell *cell = (UITableViewCell *)[[button superview] superview];
    NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
  // 继续其他的处理
}

iOS7之后出的问题

在iOS7之后,这样的办法会报错,错误在通过

1
UITableViewCell *cell = (UITableViewCell *)[[]button superview] superview];

的方式,得到的不是cell,所以在这行以下用到cell的地方,就会抛出异常。

原因是在iOS7中,在 UITableViewCell 和 UITableViewCell的ContentView之间,还有一个UITableViewCellScrollView,所以通过两次superview往上“爬”,是爬不到UITableViewCell的。

解决方式

直观地看,就是向上爬三次superview就行了。为了更好地处理这个问题,应该建立一个UIView的Category(因为这里使用的view的层次superview是UIView的方法),写个Category方法来区分不同情况来得到Cell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@implementation UIView (GetCellFromContentviewSubview)
- (UITableViewCell *)getCellFromContentviewSubview
{
    if ([[[self superview] superview] isKindOfClass:[UITableViewCell class]]) {
        return (UITableViewCell *)[[self superview] superview];
    }
    else if ([[[[self superview] superview] superview] isKindOfClass:[UITableViewCell class]]) {
        return (UITableViewCell *)[[[self superview] superview] superview];
    }
    else{
         NSLog(@"Something Panic Happens");
    }
    return nil;
}
@end

之后,把

1
UITableViewCell *cell = (UITableViewCell *)[[button superview] superview];

替换成

1
UITableViewCell *cell = (UITableViewCell *)[button getCellFromContentviewSubview];

就可以了。

讨论

另外一种办法是给控件的tag附上indexPath.row,之后在action中通过tag来得到对应的cell,但如果Cell不是静态的,indexPath.row是会变的。所以这不是一个好的办法。

但我所提供的这个办法,在stackoverflow上有1人给vote down,不知道为什么被vote down。这种办法有什么问题吗?