iPhone地图应用开发以及自定义Annotation

之前写了一篇关于获取当前位置的文章。现在这篇文章将探讨一下,如何用地图显示地理信息,以及我们怎么自定义地图的Annotation,也就是地图图钉。

接下来的叙述都是为了实现下面的效果:

上图显示的内容包括:

1. 在界面显示一副地图;

2. 在地图上显示一个“笑脸”图标,笑脸所在的位置就是你的当前位置,或者你自己任意设定的地理位置;

3. 点击“笑脸”图标之后,会弹出一个类似标签(Annotation)的东西,标签上有三种内容:左边一副蝙蝠侠的图片,中间文字内容,以及最右边的按钮。

其中2和3都是属于自定义地图Annotation。

下面我们就一步步来实现上图的效果吧。

===显示地图===

第一步:

首先在你的工程的frameworks下面加入Mapkit.framework。至于如何加入一个framework,请参考之前的一篇文章

加入Mapkit.framework之后,在需要用到地图的类的头文件中加入“#import <MapKit/MapKit.h>”。这时候便可以使用所有关于地图的功能了。

显示地图十分简单,只要在xib文件里面,将一个MKMapView拖进容纳这幅地图的View当中即可。这时候进行调试,将出现一个全球的地图。原因是因为你没有给地图一个显示的地理信息,所以初始化的地图只能显示一个全球的范围。

那么下一步便是将地图跳转到你需要显示的地理位置。加入你通过之前文章的阅读,已经获取到了当前位置,保存到checkinLocation(一个CLLocation的指针)当中了。

第二步:

在需要显示地图的View类中定义一个mapView:IBOutlet MKMapView *mapView。并且在xib中将之前拖进来的MKMapView和这个mapView成员变量建立outlets的关系(什么?不懂?那就从头学一下xcode吧)。

在类的实现文件(.m)中加入以下代码(方法名自己起吧,我这里起的是initMapView):

  1. – (void) initMapView {
  2.     MKCoordinateSpan theSpan;
  3.     //设置地图的范围,越小越精确
  4.     theSpan.latitudeDelta = 0.02;
  5.     theSpan.longitudeDelta = 0.02;
  6.     MKCoordinateRegion theRegion;
  7.     theRegion.center = [checkinLocation coordinate]; //让地图跳到之前获取到的当前位置checkinLocation
  8.     theRegion.span = theSpan;
  9.     [mapView setRegion:theRegion];
  10. }

然后,你在显示这个View的时候(比如在viewDidLoad函数中)调用initMapView,地图便可以跳到你要显示的位置了。这里关键的函数是setRegion,已经被MKMapView类封装好了。

至此,你的应用就可以显示地图,并且跳到你设定的任何位置了。是不是特简单?简直简单到我在怀疑我是否有写这篇文章的需要了。不过,废话少说。下面还是来看看如何自定义Annotation吧。

===自定义Annotation===

如果不采取自定义的Annotation,ios默认的是一个图钉的形状,如下图(一个紫色的图钉):

当然,千里执行始于足下,我们还是首先来看看如何在特定的位置显示以上的默认的小图钉吧。

第一步:

先定义一个自己的Annotation的类:

  1. @interface MyMapAnnotation : NSObject<MKAnnotation> {
  2.     CLLocationCoordinate2D location;
  3.     NSString *title;
  4.     NSString *subtitle;
  5.     NSString *headImage;
  6. }
  7. @property CLLocationCoordinate2D location;
  8. @property (nonatomic, copy) NSString *title;
  9. @property (nonatomic, copy) NSString *subtitle;
  10. @property (nonatomic, copy) NSString *headImage;
  11. @end

从上面代码可以看到,MyMapAnnotation类继承于MKAnnotation。里面包含四个成员变量。其中location保存要显示这个Annotation的位置。其他三个变量对应我们之前那个效果图的几点内容:头像、文字内容。

这个类的实现文件(.m)文件很简单,就定义好dealloc和init方法就好,这里不再赘述。

第二步:

在要显示地图的View类中实现MKMapViewDelegate,并实现viewForAnnotation函数,代码如下:

  1. – (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation {
  2.     if ([annotation isKindOfClass:[MKUserLocation class]])
  3.         return nil;
  4.     // 处理我们自定义的Annotation
  5.     if ([annotation isKindOfClass:[MyMapAnnotation class]]) {
  6.         static NSString* travellerAnnotationIdentifier = @”TravellerAnnotationIdentifier”;
  7.         MKPinAnnotationView* pinView = (MKPinAnnotationView *)
  8.         [mapView dequeueReusableAnnotationViewWithIdentifier:travellerAnnotationIdentifier];
  9.         if (!pinView)
  10.         {
  11.             // if an existing pin view was not available, create one
  12.             MKAnnotationView* customPinView = [[[MKAnnotationView alloc]
  13.                                                 initWithAnnotation:annotation reuseIdentifier:travellerAnnotationIdentifier] autorelease];
  14.             customPinView.pinColor = MKPinAnnotationColorPurple;
  15.             customPinView.animatesDrop = YES;  //如果不需要这个从天而降的效果,设置为NO即可。
  16.             customPinView.canShowCallout = NO;
  17.             return customPinView;
  18.         }
  19.         else
  20.         {
  21.             pinView.annotation = annotation;
  22.         }
  23.         return pinView;
  24.     }
  25.     return nil;
  26. }

这段代码将实现默认的一个图钉的效果。

细心地你会发现,上面的代码似乎没有涉及到地理位置,那么地图将怎么知道将这个Annotation放到合适的位置呢?稍安勿躁,稍候将会介绍如何设置Annotation的位置,同时设置Annotation的其他内容(还记得Annotation的定义里面有位置信息,还有几项内容信息吧)。

如果我不喜欢图钉,我想把它换成之前效果图中的笑脸呢?那就把上述代码改成如下:

  1. – (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation {
  2.     if ([annotation isKindOfClass:[MKUserLocation class]])
  3.         return nil;
  4.     if ([annotation isKindOfClass:[TravellerMapAnnotation class]]) {
  5.         static NSString* travellerAnnotationIdentifier = @”TravellerAnnotationIdentifier”;
  6.         MKPinAnnotationView* pinView = (MKPinAnnotationView *)
  7.         [mapView dequeueReusableAnnotationViewWithIdentifier:travellerAnnotationIdentifier];
  8.         if (!pinView)
  9.         {
  10.             // if an existing pin view was not available, create one
  11.             MKAnnotationView* customPinView = [[[MKAnnotationView alloc]
  12.                                                 initWithAnnotation:annotation reuseIdentifier:travellerAnnotationIdentifier] autorelease];
  13.             UIImage *image = [UIImage imageNamed:@”smileFace.png”];
  14.             customPinView.image = image;  //将图钉变成笑脸。
  15.             [image release];
  16.             return customPinView;
  17.         }
  18.         else
  19.         {
  20.             pinView.annotation = annotation;
  21.         }
  22.         return pinView;
  23.     }
  24.     return nil;
  25. }

从上述代码看出,也就是将定义pinColor的代码段去掉,换成设置image的代码:customPinView.image = image.

同样很简单吧。哎,我还有没有写下去的必要呢。(画外音:废话少说!!!)

第三步:

正如之前的疑问,哪里定义Annotation的地理位置以及其他内容呢?不仅仅如此,还有,怎么让用户点击笑脸(或者图钉)之后,弹出一个标签,显示定义好的内容呢。下面我们先看看最后一个问题,怎么让用户点击笑脸(或者图钉)之后,弹出一个标签,显示定义好的内容呢?

还是上面那段代,将它进一步改成:

  1. – (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation {
  2.     if ([annotation isKindOfClass:[MKUserLocation class]])
  3.         return nil;
  4.     if ([annotation isKindOfClass:[TravellerMapAnnotation class]]) {
  5.         // try to dequeue an existing pin view first
  6.         static NSString* travellerAnnotationIdentifier = @”TravellerAnnotationIdentifier”;
  7.         MKPinAnnotationView* pinView = (MKPinAnnotationView *)
  8.         [mapView dequeueReusableAnnotationViewWithIdentifier:travellerAnnotationIdentifier];
  9.         if (!pinView)
  10.         {
  11.             // if an existing pin view was not available, create one
  12.             MKAnnotationView* customPinView = [[[MKAnnotationView alloc]
  13.                                                 initWithAnnotation:annotation reuseIdentifier:travellerAnnotationIdentifier] autorelease];
  14.             customPinView.canShowCallout = YES;  //很重要,运行点击弹出标签
  15.             UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
  16.             [rightButton addTarget:self
  17.                             action:@selector(showDetails:)  //点击右边的按钮之后,显示另外一个页面
  18.                             forControlEvents:UIControlEventTouchUpInside];
  19.             customPinView.rightCalloutAccessoryView = rightButton;
  20.             MyMapAnnotation *travellerAnnotation = (TravellerMapAnnotation *)annotation;
  21.             UIImageView *headImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:travellerAnnotation.headImage]];
  22.             customPinView.leftCalloutAccessoryView = headImage; //设置最左边的头像
  23.             [headImage release];
  24.             UIImage *image = [UIImage imageNamed:@”smileFace.png”];
  25.             customPinView.image = image;
  26.             customPinView.opaque = YES;
  27.             [travellerImage release];
  28.             return customPinView;
  29.         }
  30.         else
  31.         {
  32.             pinView.annotation = annotation;
  33.         }
  34.         return pinView;
  35.     }
  36.     return nil;
  37. }

上面的代码很容易理解(加上我的注释之后),有点objective-C以及cocoa基础的朋友都会看懂。如果你看不懂,拜托,我这里不负责扫盲啊。

也许你有个疑问,似乎没见到设置文字信息呀。其实,之前自定义的MyMapAnnotation继承自MKAnnotation,所以只要你给一个MyMapAnnotation对象的title和subtitle填上内容之后,它会自动显示,不需要你手动让它显示。

下面我们就来看看如何添加MyMapAnnotation的内容吧。

其实这也十分简单,就是创建一个新的MyMapAnnotation,然后往里面添加内容即可。比如:

  1. TravellerMapAnnotation *travellerAnnotation = [[TravellerMapAnnotation alloc] init];
  2. travellerAnnotation.location = checkinlocation.coordinate;
  3. travellerAnnotation.title = [travellerNames objectAtIndex:i];
  4. travellerAnnotation.subtitle = [scores objectAtIndex:i];
  5. travellerAnnotation.headImage = [headImages objectAtIndex:i];

现在还残留的一个问题是(这是历史遗留的问题,由于某种原因,我们暂时忽略了它。。。!跑题了吧,大叔。。。):

如何将创建好的一个或者多个甚至成千上万个MyMapAnnotation加入地图并显示它呢。

这个问题更加简单。加入建立了100个MyMapAnnotations,并且全部存进了一个NSArray中,MyMapAnnotationsArray,那么我们需要做的就十分简单了:

mapView addAnnotations:MyMapAnnotationsArray

也就是调用MKMapView的addAnnotations方法将所有创建好的Annotations加进来即可。

至此(真的是至此了),所有的工作都完成了。调试,便可看到一开头展示的那个效果了。(呼,好累)。

基础性的技术文章不好写,写得详细太累人,写得简单怕误导别人,sigh。

当google满足不了你的时候

前几天偶然间发现了一个专门搜索书籍的网站:whichbook,十分有趣。在whichbook上,用户可以根据书的不同属性,或者根据自己的口味,来综合搜索某一本(或一些)你不知道名字,但知道大概的剧情或者类型的书。具体的搜索方式如下:

根据用户口味来搜索:

上图直接截取自网站。从上图来看,它将一本书的情节属性分为12类。比如暴力程度,色情程度,感情色彩等等。然后允许用户选择并调整其中的四项。如上图所示,我选择了悲伤/喜乐程度,可预知程度(最不可预知的当然是侦探小说),暴力程度,以及色情程度(Yeah!!我选的是相当色情)。用户做出这些选择之后,便可以提交选择(或者说用户的口味)进行选择。下面的两个结果是根据我的口味搜索到的(一共有几十条结果):

当然,由于都是英文书籍,这些书我都没有看过,所以我也不知搜索结果是否十分准确。

从返回结果也可以看出它们目前的一个盈利模式是:用户可以通过搜索结果直接进行购买(链接到亚马逊书店),然后进行提成(这是我猜的)。

更进一步,它还提供给用户根据书中的人物角色、情节、进行搜索。比如根据人物角色,就分为人种、年龄、性取向以及性别:

不过根据我上面的选择,搜索结果为0(神马!!哪尼!!我有那么重口味么)。

总而言之,这个网站的搜索特点,也就是和google(你可以说百度)最大的不同是:用户可以搜索到他不知道不知道的东西(这里没有打错,就是不知道不知道)。也就是说,用户可以搜索到他彻底不知道的东西。对于google搜索来说,搜一本书,你必须知道这本书的名字或者作者,或者一些大概的剧情,或者书中人物的名字。也就是说你心目中必须有那本书的意向,它是某本具体的书籍。但如果你彻底没看过某本书,或者说你心中根本没有一本书的意向,你就是想找一本超级暴力超级色情的重口味的书,那google就帮不了你了(当然,你可以直接搜索:史上最暴力的书籍,也可以出来一些结果)。

对于这类搜索,有个说法叫做:搜索用户不知道他不知道的内容。google是搜索用户知道他知道(比如搜索具体的一本书名)以及知道他不知道(比如搜索某个他不懂的算法)。对于不知道他知道的内容,这个就没必要搜索了。

但我个人(引用请交版权费)把这类搜索叫做:模糊的具体搜索(或者说不确定的确定搜索)。什么是模糊(不确定),也就是用户心目中没有一个特定的具象,他只有模模糊糊某些意向。什么是具体(确定),那就是用户心目中肯定有些具体的条件(或者说要求),就以我上述的例子来说,用户想找暴力色情内容,这就是具体的,但是他对最后的结果是没有特定的具象的。

国内有没有做这种搜索的公司呢,狭义来说是没有的,广义来说,这种“搜索”做得最好、大家都用过或者听说过的网站,就是豆瓣(神马,你没有听说过,这让阿北情何以堪啊)。当然,豆瓣的形式不是搜索(所以我称之为广义上的),它是通过推荐。基于推荐(好友的推荐,根据你自己口味的推荐)是解决搜索用户不知道他不知道的内容(确定的不确定的搜索)的最好的手段之一。原因在于,用户这种不确定性的搜索可以根据他个人的喜好来进行一定的限定,这样的搜索结果最符合用户的需求。因为搜索的本质就是为了找到自己想要的东西,即使这个东西是你之前完全不知道的。

聪明的IT从业人士(对了,比如我)来说,从这个网站肯定会想到可以扩展到很多方面的搜索,比如whichmovie(搜索电影,属性跟搜书差不多),whichrestaurant(根据个人口味搜索餐厅),whichperson(搜索异性、搜索人才)等等等等。

当然这些东西空手起家来做比较难,但如果一些大的互联网公司来做就水到渠成(当然,他们不一定会做)。比如豆瓣可以做搜书搜电影,大众点评可以搜餐厅,婚恋网站搜异性(目前根据一些具体条件来搜索就差不多有这么个意思),招聘网站搜索人才等等等等。

至于本人的创业项目,一个基于移动互联网的个人自助游产品(暂定名字叫马蹄网),从某种意义来说,也属于这种搜索,因为我们的产品能够向用户提供他在一个陌生的城市他所不知道的,但对他或许有用、他或许感兴趣的旅游信息。敬请期待。

利用CoreLocation获取地理位置以及如何在模拟器进行调试

LBS是移动应用的一个大热点。很多App都允许(或者说要求)获取用户的地理位置。这篇文章将简要谈谈如何利用CoreLocation来获取地理位置,并且会涉及到如何在iPhone的模拟器进行调试。

要利用CoreLocation,必须在frameworks里面加入“CoreLocation.framework”。在最新版本的Xcode(4.x)中加入新的framework步骤如下:

单击项目的target =>在出来的xcodeproj面板中点击“Link Binary With Libraries” =>点击“+”,然后选择需要的framework即可。

加入“CoreLocationframework”之后,就可以在类中import <CoreLocation/CoreLocation.h>,这样就可以使用所有与CoreLocation相关的类了。

下面开始在项目里面实现获取当前地理位置:

第一步:

先在类中定义两个成员变量:

  1. CLLocationManager *locationManager;
  2. CLLocation *checkinLocation;

这两个变量,locationManaager用于获取位置,checkinLocation用于保存获取到的位置信息。

第二步:

在类中实现CLLocationManagerDelegate,并实现以下方法:

  1. – (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation
  2.                         fromLocation:(CLLocation *)oldLocation {
  3.     checkinLocation = newLocation;
  4.     //do something else
  5. }

从代码可以看到,运行到这个方法的时候,locationManager已经得到了当前的位置,所以在这个方法中应该把获取到的当前位置保存到变量checkinLocation中。

第三步:

在第二步实现的方法其实是被CLLocationManager中的方法startUpdatingLocation调用的,也就是说,当程序运行 locationManager.startUpdatingLocation的时候,第二步实现的方法就会被调用。

假设点击按钮checkIn的时候,程序会获取当前位置,那么就需要在这个checkIn按钮对应的动作方法(假设是方法名就叫做checkIn)中调用locationManager.startUpdatingLocation。具体如下:

  1. – (void) setupLocationManager {
  2.     self.locationManager = [[[CLLocationManager alloc] init] autorelease];
  3.     if ([CLLocationManager locationServicesEnabled]) {
  4.     NSLog( @”Starting CLLocationManager” );
  5.     self.locationManager.delegate = self;
  6.     self.locationManager.distanceFilter = 200;
  7.         locationManager.desiredAccuracy = kCLLocationAccuracyBest;
  8.     [self.locationManager startUpdatingLocation];
  9.     } else {
  10.         NSLog( @”Cannot Starting CLLocationManager” );
  11.         /*self.locationManager.delegate = self;
  12.     self.locationManager.distanceFilter = 200;
  13.         locationManager.desiredAccuracy = kCLLocationAccuracyBest;
  14.     [self.locationManager startUpdatingLocation];*/
  15.     }
  16. }

在上面的代码中,程序首先判断机器(iPhone/iPad)是不是开启了地理位置的服务(locationServicesEnabled)。如果开启了,那么就开始进行定位([self.locationManager startUpdatingLocation];)。在定位之前要设置一些有关位置的属性,比如distanceFilter,desiredAccuacy等等。至于这些属性有什么用,可以查看xcode的文档,这里就不在赘述。值得注意的是,一定要设置locationManager的delegate是类本身,这样startUpdatingLocation运行的时候才会调用第二步实现的方法。

如果机器没有开启地理位置的服务,那么就不需要做多余的动作。注意到上面代码中有一部分代码被注释掉了,这部分代码这是为了能够在模拟器中调试程序。

下面谈谈如何在模拟器中调试获取当前地理位置的程序。

如果程序在真机中调试,只要真机开启了定位功能(GPS或者无线定位),那么就可以调试成功的。但是如果在模拟器中调试,就要做点额外功夫。由于在模拟器中是无法开启地理位置的服务(locationServicesEnabled总是等于false),所以需要将当前位置(或者任意位置)hardcode进程序当中,以便调试。

将下面的代码添加进类中(实现了获取当前位置的代码所在的类文件),添加的位置在import代码段的后面:

  1. @implementation CLLocationManager (TemporaryHack)
  2. – (void)hackLocationFix
  3. {
  4.     //CLLocation *location = [[CLLocation alloc] initWithLatitude:42 longitude:-50];
  5.     float latitude = 26.876812;
  6.     float longitude = 100.22569199999998;  //这里可以是任意的经纬度值
  7.     CLLocation *location= [[[CLLocation alloc] initWithLatitude:latitude longitude:longitude] autorelease];
  8.     [[self delegate] locationManager:self didUpdateToLocation:location fromLocation:nil];
  9. }
  10. – (void)startUpdatingLocation
  11. {
  12.     [self performSelector:@selector(hackLocationFix) withObject:nil afterDelay:0.1];
  13. }
  14. @end

添加完这段代码之后,同时将第三步实现的方法中被注释掉的代码恢复,也就是即使locationServicesEnabled等于false也调用startUpdatingLocation方法,这样程序就可以在模拟器中正常运行了。但是注意的是,这时候获取的当前位置就是是上面代码中你自己所设定的经纬度值。

整个过程都十分简单,因为大部分工作都被封装起来了。

获得当前位置之后,就可以在地图上显示,下篇文章将会谈谈如何用地图显示地理信息,包括自定义Annotation。

《getting real》摘抄之一

在项目没有进展没有头绪,在和整个team做头脑风暴做到精疲力尽的时候,开始读读《getting real》

《getting real》是大名鼎鼎的软件工作室37signals的工作笔记的总结。之所以我将之成为他们的工作笔记,是因为整本书是以他们多个成功的软件作为阐述的例子。这些都是活生生的例子,很不错。

我并不是全部赞同里面的所有观点,但是对于绝大部分,对于我们这个十分十分小的创始团队来说,是十分正确的,特别是当我们遵循“less”原则不断砍去我们产品中看似很酷其实毫无必要的功能从而慢慢看到一丝产品的希望时。

下面是对一部分《getting real》的一部分摘抄,觉得是对我们团队有用的。当然,对你,对你们团队可能毫无用处。

《getting real》摘抄之一:

那怎样才是有效的方法呢?答案是:做少。靠做得比对方少来打败他。解决简单的问题,把繁复困难棘手的问题留给大众。不做更多,相反的我们做的更少。不赶超,相反的我们试着退一步,守后。

——-

一个很好的做软件的方式就是一开始用它来解决你自己的问题。由于你自己变成了软件的目标受众因此你会知道什么是重要的什么不是。这样做下去将会是推出一个突破性产品的伟大起始点。

当你在做软件解决你自己问题的时候,你创造的工具是你对之有激情的。那激情就是关键所在。激情意味着你真正去用这个软件,去关心这个软件。这是能感动他人并一起为之所动的最好的方式。

——-

做一个比预计要小巧些的好东西比做一个庞大平庸而又漏洞百出的东西要现实的多。

范围缩小些。做半个产品比做半拉子的产品好。

——-

你能从敌人那里得到的一个好处就是:一个非常清晰的营销理念。人们很容易被冲突对立挑动。并且通过把一个产品和另一个作比较能更多地了解这个产品。选中了这么一个敌人,你给人们灌输了他们想要知道的对立的信息。这样一来,他们不仅能更好更快地认识你的产品,也会站到你的这边。这是一个吸引注意力和引发产品倾向性的一个万无一失的方法。过分地去分析其他产品会慢慢限制你的思维想像力。很快地看一下他们在做什么,然后就要回到你自己地理念和理想上来。

——-

如果你做这个软件一点不兴奋,那就是什么地方出了问题了。如果你只为了赶紧抢点钱做掉它,那这种影响会出现在最终产品上。同样地,如果你满怀热情地去做它,结果也会反映在产品上。人们是能够分辨出个中的区别的。

——-

改变是你最好的朋友,改变的代价越大,你越不可能做出改变。如果你的竞争对手可以比你更快的改变,你会处于一个很大的劣势。如果改变变得过于昂贵,你已经死了。

请记住:所有的现金,所有的营销策略,所有的世界上的优秀人物,都买不到小带来的敏捷。

对于产品的1.0版本,请从只有三个人开始。三是一个魔力数字,提供足够人力的同时允许你保持流畅和敏捷。从一个开发者,一个设计者,和一个清道夫(一个可以在开发和设计中随意切换的人)开始。

——-

竭尽全力将你的软件定位在一个点上。你的软件代表的是什么?它到底是有关什么的?在你开始设计或写任何代码之前你必须清楚地知道你做这个产品的目的 — 它的前景。把理想放大些。为什么要有它?它和其他类似产品不同的地方在哪里?

你的理念必须是简洁的。应该一句话就能把想法传达到。

——-

别在第一周就担心标题字体的大小。不需要在第二周就搞定什么是最佳的绿色的色调。更不用在第三周就要把“提交”按钮向右移动三个像素。先把该放的东西放上去。然后去用它。保证它是可用的。最后才去把它调整到完美。

细节是在你使用的过程中才会显露出来的。只有在使用中你才能看到什么需要进一步关注。在使用中你才会感到缺了些什么。常常走路绊倒脚你才会清楚地上什么坑洼是需要填补的。那些是当你被迫要留意的时候才需要的细节,不是一想到细节就去搞定它。

永远,都要从大到小去做。

——-

找到你产品的核心市场然后就专注进去

顾客并不总是对的。现实中你要能分辨出谁是你该针对的顾客,谁是你该放弃的。庆幸的是,互联网使得发掘有共识的顾客的过程变得无比容易。

如果你想讨好每个人那么你什么人也讨好不了。

如何利用Delegate来实现两个View之间的传值

两个View之间的传值可以有多种方式。但本人觉得最合理最自然并且最安全的方式是通过Delegate方式。

设想一个场景:

1. 有两个View,分别是A和B。

2. 点击A中的某个按钮,出来B。

3. 对B做某些操作(比如输入文字到textField,选择picker等等)。

4. 然后点击B中的确定按钮,将在B中进行的操作所产生的数据传递给A。

这种应用场景在编写iPhone App应用的时候是十分常见的,比如利用modelview来进行某些条件的选择,进行注册操作等等。下面具体说说利用Delegate是如何将View B中的数据传递回View A。

第一步:

在B类的类定义之前加入一下代码,定义一个delegate:

  1. @protocol ViewBControllerDelegate <NSObject>
  2. – (void) viewAWillDoThisAction:(UIViewController *) currentView;
  3. @end

这个delegate定义了一个方法,所有实现了这个delegate的类都必须实现这个方法。

然后再在B类的类定义中添加一个member,加入以下代码:

  1. NSObject<FilterSpotsViewControllerDelegate> *view_A;

从名字来看,就知道待会这个值会被赋予View A。

第二步:

让A类实现ViewBControllerDelegate:

  1. @interface ViewAController : UIViewController <ViewBControllerDelegate>

并且让A类实现方法viewAWillDoThisAction:

  1. – (void) viewAWillDoThisAction:(UIViewController *) view_B {
  2.    //利用view_B来传值。如self.a = view_B.a;
  3. }

上面代码中,将会在B类中将B类自己(self)的指针传递给view_B(见下面第四步)。这样就可以把B类的数据传递给A类了。

第三步:

就如我之前所说的,View A点某个按钮之后,出现View B。假设这个按钮对应的方法是“-(void) pushViewB: (id) sender”。那么应在这个方法中将A类自身的指针(self)传递给之前第一步在B类中定义的成员变量View_A:

  1. -(void) pushViewB: (id) sender {
  2.     //初始化一个B类,view_B
  3.     view_B.view_A = self;   //赋值
  4.     //推出View B
  5. }

第四步:

在B类,假设在View B中做完所有操作之后,点击确定按钮,返回View A,并将数据传回给view A。假设这个确定按钮的方法是-(void)done: (id) sender。那么需要在这个方法中调用A类实现的viewAWillDoThisAction:

  1. -(void) done: (id) sender {
  2. [self.view_A viewAWillDoThisAction:self];
  3. //返回View A
  4. }

上面代码注意两个问题:第一是调用view A的实现了delegate的方法,第二是将自身的指针传递给view A的方法,以方便view A能取到view B的数据。

以上就是整个利用delegate机制来进行两个view之间的传值的过程。这里注意一点:这两个view之间是有关系的(view A推出view B,从view B推出view A)。对于两个完全没有关系的view,如何传值呢?理论上利用delegate也是可以的,但并不是最好的方法。最好的方法是利用NSNotification,留到日后再说。

多嘴一点,其实说是利用delegate的方式来传值,其本质是传递类的指针来进行类成员的传值(实现了delegate的类A将自身传给了类B,然后类B又将自身传回去给类A),之所以用到delegate的机制,只不过是为了说明这个顺序:当B进行了某个操作的时候,需要A去做另外一件事。这就是delegate的本质。也就是我文章开头所说“最合理最自然”的原因。

记录创业路上的点点滴滴

八月一号正式全职创业。

有关自助游的一个移动互联网产品。

这个博客将记录创业路上的点点滴滴。包括创业的思考,包括技术的总结,等等等等。

希望做到每日一博。

无论创业成败与否,至少能给自己留点足迹。

如果这些文章能对他人有所帮助,哪怕是小小的帮助,那就更好不过了。

另外,博客页面的headimage会是我看过的比较喜欢的电影海报,会不定期更换。说不定还可以找到知音。第一幅便是我这三年看过的觉得是最好的五步电影之一《batman-the dark knight》。