mlyixi's Blog

欲望的唤醒,信仰的坚定

xml中的网页元素处理与TTStyle显示

很无奈得发现xml中有网页元素,有<br>这种编码的,也有#86br这种编码的,而一般的网页视图或TTstyle只认前一种,所以只能转换。

采用是tbxml解析xml,所以以它为例。

用到的库有GTMNSString+HTML和NSString+HTML

在TBXML中引入NSString+HTML头文件,加入+ (NSString*) textForHTMLElement:(TBXMLElement *)aXMLElement

{
    if (nil == aXMLElement->text) return @"";
	return [[[NSString stringWithCString:&aXMLElement->text[0] encoding:NSUTF8StringEncoding] stringByDecodingHTMLEntities] stringByDecodingHTMLEntities];
}

解析时调用该函数即可。

uitabbarcontroller in ios6 warning Two-stage rotation animation is deprecated

除了提示的用了two-stage rotation方法外,在设uitabbarcontroller为rootviewcontroller的appdelegate中未设置选定的selectedindex也会出现该警告。

解决方法:在初始化uitabbarcontroller后设定selectedindex=0即可。

three20 in xcode 4.5

按官方提供的脚本将three20库导入项目后各种出错。

1. _tapcount not found错误。将代码评论掉或将预处理DEBUG改成DEBUG_TOUCHES. #829

2. dyld: lazy symbol binding failed: Symbol not found: _objc_setProperty_nonatomic_copy three20-1.0.6.2无法在ios6下运行,将deploy target改成5.1。

3. self.view.width错误。引入Three20+Additions.h

ios6 simulator 报 Using two-stage rotation animation错

小白的用了xcode4.5,之前跑得好好的程序在ios6上报个Using two-stage rotation animation被遗弃,然后portaitupsidedown不支持旋转了。

其它一切正常。

但是我一点都没用two-stage的方法啊。。。

汗,还是用回ios5.1...

frame bounds理解

原文见http://ashfurrow.com/you-probably-dont-understand-frames-and-bounds

每个视图控制器都有一个在其视图层次最下层的视图属性(self.view),这个视图可以有子视图。

self.view的范围是从状态栏下面到属于该控制器的下界。如下图,其下界就是tabbar上方。这是因为tabbar是属于tabbarcontroller的,层次上算是该视图控制器的superviewcontroller. 而上界为什么是状态栏下面就可以理解了。如果在interfacebuilder里看self.view的属性,则可以看出其frame是(0,20,宽度,高度)。注意,由于xib显示不了父控制器,就算是在ib中选择了bottombar,高度也是460.

在ios4及以前,你只能在一个视图控制器中加载子视图,这样写代码管理这些视图显示就有些难了.

在ios5,你可以在一个视图控制器中加载子视图控制器中的视图

下面说下frame与bounds的区别

一言以蔽之,就是frame是指视图在父视图坐标系中的坐标,bounds是指在自已坐标系中的坐标.所以加载子视图的过程就了然了.

 

 

ios开发中显示网页元素的一些问题

如果文本中有网页元素(如换行,特殊字符,图片链接等),在处理文本显示上就显得比较麻烦了,特别是要将这些文本放在uitableviewcell中时,涉及cell重用,也许只有采用三方提供的uilabel才可能同时在cell重用的同时显示网页元素. 利用uiwebview和uitextview都不太能将把两者解决好.

方案:

1. uiwebview. 利用uiwebview显示网页元素比较简单,只要解析文本就好了. 尝试用了这里提供的解码库,采用的是GTM.注意解码的层次,uiwebview能够解析一层而已.如&amp;&lt;br&gt;需要解析2次才成转成<br>然后在uiwebview中显示换行. 如果只解析一次,在uiwebview中就只显示<br>了. 

2. uitextview. 利用未公开的实例方法setcontexttohtmlstring可以实现webview的功能,但是据说苹果不允许这样做.同时试了下在uitableview中显示简单的网页元素,结果打开到显示出来延时半秒到1秒左右,影响效率.

3. 三方库,如three20的styledtextlabel. 没试,主要是three20各组件都集成在一起. 把整个three20都加入现有的程序中觉得不太好.有空试着学着用.

 

uiwebview与uitableview

将uiwebview加入到uitableviewcell中在uitableview中显示的时候有如下几个问题:

1. tablecell的高度问题. 

因为uitableview的delegate中高度的决定是在cell加载之前,而webview的最佳尺寸得当webview加载完后(其delegate的uiwebviewdidfinishload方法中,两种方法sizethatfits和javascript)才能得到,所以这里就有一个矛盾. 尝试了下通过自定义delegate将webview加载完后得到的最佳尺寸传递到uitableview中然后table重新加载(示例代码),结果导致了无限循环(原因见这里).

另一种方法是根据文本猜测uiwebview的高度, = =.由于uiwebview的字体和大小都不一样,所以要文本字体及大小的设置都要自己决定. 据说苹果也是这么做的,将cell的高度预先定义到一个表中然后使用.

2. tableviewcell的重用问题.

当高度的问题解决后,接着是cell重用.如果tablecell只是显示信息,而没有didselectedcellAtRowIndexPath然后pushviewcontroller的话,就应该没有重用问题.但是如果选择并进入下一个视图控制器,返回后再滚动table,重用问题就显示出来了----选择的那个cell的frame加载了多次文本. 可能是由于下一视图控制器retain了该cell中的对象,原因不明.

所以只好舍弃tabelcell重用,直接cell=[tablaeviewcell alloc] init],一切就正常了.

 

 

 

loadView/viewDidLoad/initWithNibName/awakeFromNib/initWithCoder的用法总结

首先,区别程序化初始viewcontroller,半程序化初始和IB初始的区别.

1. 代码化初始:完全没有Xib之类的东西,通过纯代码实现加载.

2. 半代码化:设计xib,然后在程序中用代码调用xib来初始化.

3. 完全IB化:设计xib之类,然后加入到其它xib里.如MainWindow.xib中加入rootviewController,而rootviewcontroller通过xib设计.或者tabbarcontroller中加入多个由xib设计的viewcontroller,这些都是通过IB来初始化的.

好了,了解了这些,来看看各种方法的使用.

1. viewDidLoad:这个方法在三种方式下都会调用,而且是加载完view后调用.

2. loadView:代码初始化程序时设计view用. 半代码化时和完全IB化初始时亦调用,但会重写xib中的view,调用在initwithNibName之后,viewDidLoad之前.

3. initWithNibName:半代码化初始时使用. 完全IB化初始时不调用,UIViewController代码化初始时会通过init调用,且调用在[super init]中. 

 

4. awakeFromNib:awakeFromNib这个方法是一个类在IB中被实例化是被调用的.看了帖子发现大家都推荐使用viewDidLoad而不要使用awakeFromNib,应为viewDidLoad会被多次调用,而awakeFromNib只会当从nib文件中unarchive的时候才会被调用一次.实际测试中发现,当一个类的awakeFromNib被调用的时候,那么这个类的viewDidLoad就不会被调用了,这个感觉很奇怪.

5.initWithCoder是一个类半代码化实例时被调用的.比如,通过IB创建一个controller的nib文件,然后在xocde中通过initWithNibName来实例化这个controller,那么这个controller的initWithCoder会被调用.

 

调用顺序: [super init](可选)->initWithNibName(其实在super init中) ->int after self created->loadView->ViewDidLoad

ios内存管理总结 (非ARC)

一. 变量与属性

1. interface中定义的变量为指针,在实现文件中指向对象,若没有定义它的property,则无引用计数.(应该是这样的吧).

2. interface中定义的属性根据修饰符管理内存对象,其属性其实是变量的setter和getter,同时根据修饰符附加相应的release和retain方法.如retain(strong)修饰符会在setter中定义

- (void)setCount:(NSNumber *)newCount {    
    [newCount retain];
    [_count release];
    // Make the new assignment.
    _count = newCount;
}

而resign,weak则不会retain. copy会对复制的对象retain.

3. 变量与属性.如上所述,如果只要变量指向已有的对象,则不用指定其属性.而定义了属性,则必有变量,不论是显示的synthesize p=_p 或隐式的synthesize p.

其中_p为指定的变量名,而隐式的,则p同时为变量名. 这里要注意p和self.p的区别, p只是变量指针,而self.p其实是调用了setter,这就和修饰符有关了.

 

二.对象生命周期(alloc,init,retain,release,release)

1. 对于alloc/new/copy/mutablecopy产生的对象要负责release掉,因为这些方法会加引用计数.

2. 其它方法创建的对象,如类工厂方法(+), (id)以及带error的,产生的引用不需要主动释放,如果没有主动retain的话.

3. 函数返回值.如在函数中定义的变量需要返回给调用者,则使用autorelease方法.

 

三.IB中的对象

1. 苹果推荐一般使用weak,但是需要自定义的,如要隐藏的view,则只能使用retain.

2. 由于viewcontroller用kvo设置所有outlets是强引用的,所以如果一个控制器被解除了,top-views也将自动释放.

3. 这时有两种策略:要么retain所有outlets,然后在viewDidUnLoad,dealloc中释放解除之.要么把所有outlets都定义为弱.但显然,每二种在某些功能上不现实.

多线程和GCD初学者教程

原文链接:http://www.raywenderlich.com/4295/multithreading-and-grand-central-dispatch-on-ios-for-beginners-tutorial

当不使用多线程时,UI(主线程)经常变得不可响应. 该教程针对有相关开发经验者介绍多线程,如果是新手,请先查看更多教程.

一.为什么关注?

首先通过一个没有用多线程的实际的例子进行说明.

下载该工程,用xcode打开并运行,你将在屏幕上看到一个免费的游戏包(网页).

这个app的任务是把该网页中的所有图片都下载下来,并把它们在一个tableview中显示出来.

运行后你会发现该app经常无法响应用户操作,如grap按钮.

因为该app解析html,下载图片和zip文件,解压等操作都在主线程中进行.

二.多线程...和猫!(作者爱猫?)

如果你已经熟悉多线程的概念,那么就跳到下一节吧,否则,请继续.

当你想像程序的运行时,你可以认为是一只带着箭头的猫,这箭头指向某一行代码....(汗). 这只猫移动这个箭头使得程序运行.

app的关键问题是这只可怜的猫已经精疲力尽了,因为它又要管UI(响应用户事件)和大量数据处理.

多可怜啊,它需要休息.

解决文案很简单:再买几只猫.(买猫怕养不活哦).

现在,你的第一只猫负责更新UI和响应用户事件,而其它猫负责暗地里的下载文件,解析html等等等等.

这个就是多线程的要旨.

在ios中,你经常要实现的方法如viewDidLoad,button tap callbacks等都运行在主线程中,不要把费时的工作放到主线程中,不然你会得到一个不响应UI和精疲力尽的猫.

三.孩子,不要在家做这个.

让我们看看现有的代码并且讨论它是怎么工作的.

app中的rootViewController是WebViewController,当你点grabTapped按钮,它获取当前页的html并传给imageListViewController.

在imageListViewController中的viewDidLoad中,它创建一个新的imageManager并处理之.这个类,还有imageInfo,包含了所有的费时代码.

让我们看看这两个类是如何工作的:

1.ImageManager:processHTML:用正则表达式匹配来搜索html中的链接.

2.ImageInfo:initWithSourceURL:调用getImage来获取图片,通过[NSData dataWithContentsOfURL:..]同步进行.

3.ImageInfo:retrieveZip:同2来获取zip文件

4.ImageInfo:processZip:调用zipArchieve库进行解压缩.

其它的一些方法就不介绍了.主要就是理解它如何工作的,以便让我们改进.

四.异步下载.

首先让我们用异步下载来代替这个最慢的操作--下载文件.

其实用苹果内建的NSURLRequest和NSURLConnection并不难,但是使用ASIHTTPRequest更简单.

下载和添加该类就不介绍了.

现在是时候进行替换了.

在ImageManger.m中:

 

// Add to top of file
#import "ASIHTTPRequest.h"
 
// Replace retrieveZip with the following
- (void)retrieveZip:(NSURL *)sourceURL {
 
    NSLog(@"Getting %@...", sourceURL);
 
    __block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:sourceURL];
    [request setCompletionBlock:^{
        NSLog(@"Zip file downloaded.");
        NSData *data = [request responseData];
        [self processZip:data sourceURL:sourceURL];        
    }];
    [request setFailedBlock:^{
        NSError *error = [request error];
        NSLog(@"Error downloading zip file: %@", error.localizedDescription);
    }];
    [request startAsynchronous];    
}

该代码实现了当异步下载后可能要执行的两个Block.

同理,在ImageInfo.m中

 

// Add to top of file
#import "ASIHTTPRequest.h"
 
// Replace getImage with the following
- (void)getImage {
 
    NSLog(@"Getting %@...", sourceURL);
 
    __block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:sourceURL];
    [request setCompletionBlock:^{
        NSLog(@"Image downloaded.");
        NSData *data = [request responseData];
        image = [[UIImage alloc] initWithData:data];
    }];
    [request setFailedBlock:^{
        NSError *error = [request error];
        NSLog(@"Error downloading image: %@", error.localizedDescription);
    }];
    [request startAsynchronous];    
}

运行之,app马上转到detail tab,但是还有一个问题:图片没有显示.

五.介绍NSNotifications.

传更新从一段代码到另一段代码,使用NSNotification是一种很简单的方法.

1.当你完成一任务后想通知给其它代码段,调用postNotificationName.

2. 当你想知道更新是否完成时,使用addObserver:selector:name:object.

3.用完后请记得removeObserver:name:object.

现在,打开ImageInfo.m

 

// Add inside getImage, right after image = [[UIImage alloc] initWithData:data];
[[NSNotificationCenter defaultCenter] postNotificationName:@"com.razeware.imagegrabber.imageupdated" object:self];

ImageListViewController.m

// At end of viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(imageUpdated:) name:@"com.razeware.imagegrabber.imageupdated" object:nil];
 
// At end of viewDidUnload
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"com.razeware.imagegrabber.imageupdated" object:nil];
 
// Add new method
- (void)imageUpdated:(NSNotification *)notif {
 
    ImageInfo * info = [notif object];
    int row = [imageInfos indexOfObject:info];
    NSIndexPath * indexPath = [NSIndexPath indexPathForRow:row inSection:0];
 
    NSLog(@"Image for row %d updated!", row);
 
    [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
 
}

这样,图片更新就完成了.

六.Grand Central Dispatch and Dispatch Queues.

这样还有一个问题:解压zip还在主线程中.

有空再翻译....

 

ios without interface builder 开发

一.视图

把自定义视图控制器赋给Delegation-window-rootViewController,在自定义视图控制器的loadView方法中初始化view.

@class NOIBViewController;

@property (strong, nonatomic) NOIBViewController *viewController;

 

self.window.rootViewController = viewController;

 

 

@property (strong, nonatomic) UILabel *label;

 

- (void)loadView
{
    CGRect frame = CGRectMake(0, 0, 320, 480);
    self.view = [[UIView alloc] initWithFrame:frame];
    self.view.backgroundColor = [UIColor whiteColor];
    frame = CGRectMake(0, 0, 100, 50);
    label = [[UILabel alloc] initWithFrame:frame];
    label.text = @"hello world";
    [self.view addSubview:label];
}

二. 事件响应

 

- (void)loadView
{
    frame= CGRectMake(20, 80, 280, 50);
    button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    button.frame=frame;
    [button setTitle:@"OK" forState:UIControlStateNormal];
    button.backgroundColor = [UIColor clearColor];
    [button addTarget:self action:@selector(btnClicked:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}

-(IBAction)btnClicked:(id)sender
{
    Some codes
    //删除自身视图
    [self.view removeFromSuperview];
    //加载子视图
    [self.view addSubview:UserViewController.view];
}

 

三.navigationController

设置nav的initWithRootViewController为自定义视图控制器, 通过pushViewController方法加载其它视图控制器,一般popViewController自动实现--即为左侧按钮. 右侧按钮需要手动添加(NavigationItem.rightBarButtonItem).

 

    nvc = [NumberViewController controllerWithNumber:1];
    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:nvc];
    window.rootViewController = nav;

 

+ (id) controllerWithNumber: (int) number
{
    NumberViewController *viewController = [[NumberViewController alloc] init];
    viewController.number = number;
    viewController.textView.text = [NSString stringWithFormat:@"Level %d", number];
    return viewController;
}


- (void) pushController: (id) sender
{
    NumberViewController *nvc = [NumberViewController controllerWithNumber:number + 1];
    [self.navigationController pushViewController:nvc animated:YES];
}

- (void) viewDidAppear: (BOOL) animated
{
    self.navigationController.navigationBar.tintColor = COOKBOOK_PURPLE_COLOR;
    
    // match the title to the text view
    self.title = self.textView.text; 
    self.textView.frame = self.view.frame;
    
    // Add a right bar button that pushes a new view
    if (number < 6)
        self.navigationItem.rightBarButtonItem = 
        BARBUTTON(@"Push", @selector(pushController:));
}

 

四.SplitViewController