第三章 基础交互
类似于Hello world的一个程序
1. 意识到了Action中sender这个变量的作用,之前从来没用过这个变量的。。。
lblShow.text= [NSStringstringWithFormat:@"%@ button pressed.", [sendertitleForState:UIControlStateNormal]];
这里可以直接获得按下的那个按钮上面的文字
2. 在iOS 4中通常习惯用alloc分配一个新的NSString,initWithFormat后使用,然后用完再release,在上一个版本的书里都是这样写的。不过新版的书都将其改为了直接调用类方法stringWithFormat。我以为这是iOS 5的新函数,一查文档在iOS 2时就有了,不知道为啥现在才拿来用。网上的说法是,这个类方法也会申请内存,但是autorelease的,这样说来iOS 4也可以这样写嘛,可
为什么到现在才改成这样呢?难道跟iOS 5的ARC有关么。。。
3. Product菜单中的Analyze命令可以分析并发现内存泄露等缺陷,貌似很好用,之前没发现。
第四章控件大杂烩
1. 学会了一个常用的快捷键,Command + =,让控件大小适应内容。如果一个Label要显示长度会变的内容时,在设计界面时就先敲入可能的最长内容,然后按这个快捷键,控件的大小就刚好合适了。
2. 按背景隐藏键盘,这个我之前的做法一直是插入一个透明的Custom按钮,放大到全屏,再加Action,还要调整层叠次序。这章介绍的方法是直接将视图的基类从默认的UIView改成UIControl,这样视图本身就可以响应事件了~太神奇了!而且超方便~
3. 为了节省资源,控件的Clip Subviews和Clears Graphics Context选项要关掉,Opaque选项不影响视觉效果的话能开就开
4. 之前不知道拖控件时穿过Text Box的那条参考线是什么意思,现在知道那叫Baseline guide,就是输入的文本的基线。类似于初中写作业用的凯撒牌英语本上的那条红线,哈哈
5. 提示信息用UIAlertView,里面不能放太多按钮。通过
[alertshow];显示。让用户选择用UIActionSheet,可以放多个按钮。通过
[actionSheetshowInView:self.view];显示。得到用户按了哪个按钮,都需要通过delegate。这种阻断程序,让用户做选择的东西统称modal view,貌似是翻译成“模态视图”
6. 继续体会到了sender的强大,功能类似的一组控件基本上都可以将事件响应写到一个Action响应的函数里,然后根据sender判断按了哪个。还有在slider滑动时的action中直接根据sender获得其value,而不需要为其建立专门的outlet。另外判别是哪个控件触发时,可以利用控件的tag字段
7. 可伸缩图片!!超实用的技巧!!设置不可伸缩部分(Cap),然后剩余部分就被自动拉长,填满控件。书上的代码还不太完善,他将topCapHeight设为了0,导致按钮很高时,两条水平的边缘变得很丑。我将topCapHeight也设为了12,大按钮看起来就很美观了。代码比较经典,直接贴上来备忘吧!用的时候别忘了把Button的类型从Round Rect改为Custom
UIImage*imageNormal = [UIImageimageNamed:@"whiteButton.png"];
UIImage*strhNormal = [imageNormalstretchableImageWithLeftCapWidth:12topCapHeight:12];
UIImage*imageHighlighted = [UIImageimageNamed:@"blueButton.png"];
UIImage*strhHighlighted = [imageHighlightedstretchableImageWithLeftCapWidth:12topCapHeight:12];
[dosthButtonsetBackgroundImage:strhNormalforState:UIControlStateNormal];
[dosthButtonsetBackgroundImage:strhHighlightedforState:UIControlStateHighlighted];
8. 在Action响应函数中,那些以Index标志的东西要加上注释,提高代码的可读性。比如index为0的segmented control是哪一个,比如ActionSheet中的第1个按钮是什么含义等等。
第五章 自动旋转
1. 先学学英语吧,protrait指的是竖屏(又高又窄),landscape指的是横屏(又矮又宽)
2. 建立一个iPhone工程,默认的Supported Device Orientations是除了“Upside down”以外的三个,而建立一个iPad工程时,默认则是支持全部的四个方向。这是为什么呢?因为iPhone在使用过程中面临突然有电话打进来的情况,用户没法在设备倒置的情况下打电话,因为话筒和听筒安装在电话的两端,没法混用,这就造成了不方便。而iPad则不存在电话和听筒位置的问题,所以默认是支持四个方向。一个细节而已,没啥用,就是挺吃惊的。
3. 支持多个方向最简单的方法是修改控件Autosizing的方式,图形化的界面,非常方便。但在复杂情况下不能满足需求,就要写代码手动调整控件的位置和大小。如果两个方向的界面相差很大,可以在一个Nib里建立两个View,分别制作好,通过代码切换这两个View。按Alt键拖动View在侧边栏里的条目可以复制View。
4. 通过
willAnimateRotationToInterfaceOrientation响应设备旋转。这里涉及的技术稍微复杂一些,切换view只需要为View建立outlet并赋值给self.view,重要的是view的定位和方向。开始我没明白为什么要旋转,如果不明白的话,把给transform赋值的那几句话去掉,再运行一下看效果,你瞬间就会明白的~!这里利用的是图形学中的旋转矩阵,当年学OpenGL生不如死的现在貌似用上了,一看那个先搞成单位矩阵,再搞仿射变换,瞬间感到一种亲切感!这段代码也挺经典的,以后可以直接拷贝来用,这里收藏一下~
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation) interfaceOrientation duration:(NSTimeInterval)duration {
if(interfaceOrientation ==UIInterfaceOrientationPortrait)
{
self.view=self.portrait;
self.view.transform=CGAffineTransformIdentity;
//self.view.transform = CGAffineTransformMakeRotation(0);
self.view.bounds=CGRectMake(0.0,0.0,320.0,460.0);
}
elseif(interfaceOrientation ==UIInterfaceOrientationLandscapeRight)
{
self.view=self.landscape;
self.view.transform=CGAffineTransformIdentity;
self.view.transform=CGAffineTransformMakeRotation(0.5*M_PI);
self.view.bounds=CGRectMake(0.0,0.0,480,300);
}
elseif(interfaceOrientation ==UIInterfaceOrientationLandscapeLeft)
{
self.view=self.landscape;
self.view.transform=CGAffineTransformIdentity;
self.view.transform=CGAffineTransformMakeRotation(-0.5*M_PI);
self.view.bounds=CGRectMake(0.0,0.0,480,300);
}
}
需要注意的有(1)仿射变换的参数是弧度制,不是角度制 (2)RectMake的时候要考虑状态栏的高度 (3)直立状态时也要为transform赋值,将界面转回正常,否则开始运行时是正确的,横过来再竖过来就错了
5. iOS的新特性outlet collection,Outlet具有一对一的特点,一个Outlet只能对应一个控件,一个控件对应多个Outlet也没有意义。为了能让一个Outlet控制多个控件,新版iOS(据说是iOS 4引入的?不过在这本新版iOS 5的书上才讲到,上一个版本的书没讲)引入了Outlet Collection这个概念,想建立控件-Outlet多对一的关系时,可以创建Outlet Collection,它实际上就是一个Outlet的数组,类型是NSArray。可以用for...in循环来遍历,用containsObject实例方法来查找。
下面代码展示了将一个Outlet Collection中所有的控件的属性全部改变的循环
if([topscontainsObject:sender]){
msg =@"Top button pressed.";
for(UIButton*btnintops){
btn.hidden=YES;
}
}
6. 一个新的关键词,strong,没懂啥意思,后来在StackOverflow上搜到了一个很好的解释
我就来翻译一下吧:它作为Objective-C自动引用计数(ARC)的一部分,用来替换retain修饰符。在非ARC代码中,它就是retain的同义词。
第六章 多视图应用
很长的一章,代码挺多的,第一次从空工程构建出一个App。。。
1. 在AppDelegate里加载第一个ViewController的时候要计算status bar的高度。但是直接建出的工程就没有这个步骤,不知道为啥。。。计算的代码是
self.window= [[[UIWindowalloc]initWithFrame:[[UIScreenmainScreen]bounds]]autorelease];
SwitchViewController*switchController = [[SwitchViewControlleralloc]initWithNibName:@"SwitchView"bundle:nil];
self.switchViewController= switchController;
UIView*switchView =self.switchViewController.view;
CGRectframe = switchView.frame;
frame.origin.y+= [UIApplicationsharedApplication].statusBarFrame.size.height;
switchView.frame= frame;
[self.windowaddSubview:switchView];
self.window.backgroundColor= [UIColorwhiteColor];
[self.windowmakeKeyAndVisible];
[switchControllerrelease];
returnYES;
2. 这里再说一下内存管理吧。。。到第六章才意识到这个问题显然有点晚。通过这本书新旧两个版本的比较阅读,我发现最大的区别就是内存管理。由于ARC的引入,前面版本里跟内存管理有关的讲解和代码在新版里消失的无影无踪,之前各种经典的内存泄露在新版里则成为了范例代码。这些代码搬到iOS 4可以用肆无忌惮来形容,哈哈!不过iOS 5不会那么快的普及,因此我们还是应该了解iOS在内存管理方面的特点,避免内存泄露。
就拿上面的那段代码来说,要注意的是
SwitchViewController*switchController = [[SwitchViewControlleralloc]initWithNibName:@"SwitchView"bundle:nil];
self.switchViewController= switchController;
C++用户可能会疑惑,为什么要switchController这个指针多此一举呢?下面这样一句话解决不行吗?
self.switchViewController= [[SwitchViewControlleralloc]initWithNibName:@"SwitchView"bundle:nil];
这是Objective-C的特性决定的,属性switchViewController是由retain修饰的,即对赋值操作自己持有一个副本。这样alloc出来的对象占用一块内存,属性switchViewController由于持有该对象就再占用一块内存。类析构时只能释放后者,而前者则泄露。
因此在OC里,如果不开ARC,那么那个临时的指针变量以及随后的release语句就是必须的了。
3. 分别建立nib和代码文件时,需要建立两者的联系。需要做的有(1)点nib里的File‘s owner,设置其Custom Class为其Controller类的名字 (2)将View与File's owner里的view建立outlet。这两项做完就可以在Assistant界面上拖Outlet和Action了
4. 一点
疑问,书上说superview有status bar了,那么subview就不应该设置status bar,这样系统才能正确计算出尺寸。我实验中恰好相反,在subview里加上了status bar,显示出的控件位置才跟Interface Builder里的一样。
5. 视图切换的动画。AnimationCurve的概念,Animation block的概念。
第七章 TabBar和DataPicker
1. 学会了如何手动从空工程建立Tabbed Application。可以直接在AppDelegate里载入TabBar View Controller的xib,代码只要在
didFinishLaunchingWithOptions方法中加两行
[[NSBundlemainBundle]loadNibNamed:@"TabBarController"owner:selfoptions:nil];
[self.windowaddSubview:rootController.view];
当然别忘了把xib中File's owner的Class改成AppDelegate,还有将TabBar Controller与手动敲的
@property(strong,nonatomic)IBOutletUITabBarController*rootController;
建立outlet连接
2. Tab中每个Item除了图标和Title之外还要改两处(1)Custom Class (2)Nib Name
都改成要载入的那个View的类名和xib名
3. Picker里最常用的一个方法是
selectedRowInComponent
4. Picker的两个协议(代理和数据源)中,必须要实现的方法有3个
(1)
- (NSInteger) numberOfComponentsInPickerView:(UIPickerView*)pickerView
(2)
- (NSInteger)pickerView:(UIPickerView*)pickerView numberOfRowsInComponent:(NSInteger)component
(3.1)
- (NSString*)pickerView:(UIPickerView*)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
(3.2)
- (UIView*)pickerView:(UIPickerView*)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView*)view
其中(3.1)和(3.2)可以选一个实现。可选的方法是:
- (void)pickerView:(UIPickerView*)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
必选的三个方法记忆方法就是2个NSInteger和一个NSString*,只有记住第一个括号,然后敲Esc补全就可以了。还跟新浪网院的老师学了个歪招,就是按住Command键,点头文件里的协议名,看协议的声明,然后复制协议方法的原型到自己的代码中。
5. 用
NSDate*selected = [datePickerdate];获取Date Picker的日期数据,但返回的是中央时区的时间,貌似还需要Format。这里犯过一个低级错误,就是把NSDate写成了NSData,不仅能过编译,而且还能正常运行!只是这句话上一直有警告。端详了老半天才发现一个字母的差异。。。
6. Double Picker那个View写起来明显熟练了很多,基本上没出啥问题,书瞄上一眼就能自己写出来。
7. plist读进来是一个Dictionary,用valueForKey方法查找
8. 如果picker的component之间有依赖关系,需要在didSelectRow协议方法中调用picker的reloadComponent方法来重新载入需要改变的component,或者直接reloadAllComponent。。。
9. picker有多个component时,可以把每一列的编号define成一个宏常量,这样判断时就可以用有意义的字符串代替编号,提高代码的可读性
10. 通过widthForComponent方法可以指定列宽,从而形成各个component不等宽的效果,如果几个Component数据长度差异较大,可以考虑实现这个方法。看书上的代码,各个component的宽度之和为
290个像素。另外还有一个指定每行高度的方法叫rowHeightForComponent,还没用过。
11. 一些需要收藏备用的代码
(1) 用代码选定picker中的某一行
[pickerselectRow:0inComponent:kZipComponentanimated:YES];
(2) 上一条的逆操作,即得到现在选中了哪一行
[pickerselectedRowInComponent:kStateComponent]
(3) 对NSArray排序(实例名为sorted)
sorted = [sortedsortedArrayUsingSelector:@selector(compare:)];
(4) 获取资源的NSURL:
NSURL*resURL = [[NSBundlemainBundle]URLForResource:@"文件名"withExtension:@"扩展名"];
(5) 通过NSURL将PList读入到NSDictionary:
NSDictionary*dict = [[NSDictionaryalloc]initWithContentsOfURL:resURL];
(6) 从资源中载入图片
UIImage*seven = [UIImageimageNamed:@"seven.png"];
(7) 动态的通过属性的名称为其赋值
[selfsetValue:新的值forKey:本类中的属性名];
(8) 延迟一会儿再调用某个方法:
[selfperformSelector:@selector(要调用的方法名)withObject:nilafterDelay:0.5];
想不到SDK里居然有这种奇葩的函数。延迟的单位是秒。对另一个函数的调用是同步的,也就是说如果被调的函数执行时间很长,那这样调用仍然会卡住UI线程
(9) 播放声音
NSURL*soundURL = [[NSBundlemainBundle]URLForResource:@"声音文件名"withExtension:@"wav"];
SystemSoundIDsoundID;
AudioServicesCreateSystemSoundID((__bridgeCFURLRef)soundURL, &soundID);
AudioServicesPlaySystemSound(soundID);
那个斜体的__bridge是给ARC看的,iOS4中不用加。使用这些代码需要加头文件
#import<AudioToolbox/AudioToolbox.h>,当然还需加框架AudioToolbox~,还记得作为一名文艺青年应该怎么加框架吗?
重要提示:通过与iOS4旧版图书的比较阅读,发现以上播放声音的代码在没有ARC的时候会导致内存泄露!在没有ARC的情况下,正确的做法是将SystemSoundID作为类的一个属性,在视图载入时就调用
AudioServicesCreateSystemSoundID方法创建SoundID,在视图退出时调用
AudioServicesDisposeSystemSoundID(soundID);方法释放。奇葩的是,SoundID不能在播放的函数里释放,SoundID必须在播放期间都有效。否则会放不出声音。不这样释放的话,在声音被反复打开时就会出现内存泄露。还有,SoundID貌似一个类,其实就是一个无符号长整型数。
终于读完了这一章!!!好长
第八章 TableView初步
1. 还是说说内存管理吧。self.XXX = YYY; 跟 XXX = YYY; 其中XXX是声明为retain的属性。这两种写法是不一样的。前者会发挥retain的作用,而后者直接给指针赋值。如果你随后又将YYY给release了的话,那第二种写法就会出错,必须使用第一种。还有,在读取一个属性的时候,加不加self是没区别的~
2. 这一章后续的内容是(1)右侧的索引条 (2)基于Category(分类)的深拷贝 (3)搜索栏. 暂时还用不到,等回头再看吧
第九章 Navigation Controller和TableView
未完待续。。。
第十一章 定位服务(LBS)
1. Core Location框架使用很方便,经纬度、海拔、精度、旅行距离都能直接测出来
2. 编译的时候要加上CoreLocation.framework,加这个框架有3三种方法
普通青年:在原有的框架比如UIKit.framework点右键,在打开的文件夹里找到CoreLocation.framework,然后拖进Xcode里,在向导里点确定
文艺青年:在项目设置的页面点Build Phase,在Link Binary with Libraries下面点加号,选CoreLocation.framework加进来
2B青年:在项目上右键,Add files to “XXX”,然后依次展开/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk/System/Library/Frameworks/找到CoreLocation.framework加进来
话说我就是差点成了2B青年,最后用普通青年的方法加进来的,书上介绍的方法属于文艺青年~~哈哈哈
3. 程序的代码也是比较固定,可以直接粘贴到需要定位的项目里用,这里也贴上来收藏
头文件如下,需要加的有1头文件,2协议,3需要的Outlet
#import<UIKit/UIKit.h>
#import<CoreLocation/CoreLocation.h>
@interfaceFirstViewController :UIViewController<CLLocationManagerDelegate>
@property(retain,nonatomic)CLLocationManager*locManager;
@property(retain,nonatomic)CLLocation*startPoint;
@property(retain,nonatomic)IBOutletUILabel*lblLatitude;
@property(retain,nonatomic)IBOutletUILabel*lblLongitude;
@property(retain,nonatomic)IBOutletUILabel*lblHorAccu;
@property(retain,nonatomic)IBOutletUILabel*lblAltitude;
@property(retain,nonatomic)IBOutletUILabel*lblVerAccu;
@property(retain,nonatomic)IBOutletUILabel*lblDistance;
@end
.m实现文件
在视图载入后初始化
CLLocationManager、设定精度、更新位置信息,这是定位最核心的代码,却非常简洁
- (void)viewDidLoad
{
[superviewDidLoad];
locManager= [[CLLocationManageralloc]init];
locManager.delegate=self;
locManager.desiredAccuracy=kCLLocationAccuracyBest;
[locManagerstartUpdatingLocation];
}
每次更新得到新的坐标后,就会调用这个方法。坐标啥的都在newLocation里了,直接拿来用就行。还有别忘了处理一下错误,如果用户没有授权获取位置信息,那就要做响应的处理。
#pragma mark -CLLocationManagerDelegate Methods
- (void)locationManager:(CLLocationManager*)manager
didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation {
NSString*latitudeString = [NSStringstringWithFormat:@"%g\°", newLocation.coordinate.latitude];
lblLatitude.text= latitudeString;
NSString*longitudeString = [NSStringstringWithFormat:@"%g\°", newLocation.coordinate.longitude];
lblLongitude.text= longitudeString;
NSString*horizontalAccuracyString = [NSStringstringWithFormat:@"%gm", newLocation.horizontalAccuracy];
lblHorAccu.text= horizontalAccuracyString;
NSString*altitudeString = [NSStringstringWithFormat:@"%gm", newLocation.altitude];
lblAltitude.text= altitudeString;
NSString*verticalAccuracyString = [NSStringstringWithFormat:@"%gm", newLocation.verticalAccuracy];
lblVerAccu.text= verticalAccuracyString;
CLLocationDistancedistance = [newLocationdistanceFromLocation:startPoint];
NSString*distanceString = [NSStringstringWithFormat:@"%gm", distance];
lblDistance.text = distanceString;
}
- (void)locationManager:(CLLocationManager*)manager didFailWithError:(NSError*)error {
NSString*errorType = (error.code==kCLErrorDenied) ?@"Access Denied":@"Unknown Error";
UIAlertView*alert = [[UIAlertViewalloc]initWithTitle:@"Error getting Location"message:errorTypedelegate:selfcancelButtonTitle:@"Okay"otherButtonTitles:nil];
[alertshow];
}