notes
notes copied to clipboard
Beginning iOS 8 Programming with Swift 读书笔记
1. 设置图片圆角
thumbnailImageView.layer.cornerRadius = thumbnailImageView.frame.size.width / 2
thumbnailImageView.clipsToBounds = true
2. UIAlertController
// Create an option menu as an action sheet
let optionMenu = UIAlertController(title: nil, message: "What do you want to do?",preferredStyle: .ActionSheet)
// Add actions to the menu
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
optionMenu.addAction(cancelAction)
// Display the menu
self.presentViewController(optionMenu, animated: true, completion: nil)
声明并创建闭包,填充UIAlertAction的handler
let callActionHandler = { (action:UIAlertAction!) -> Void in
let alertMessage = UIAlertController(title: "Service Unavailable", message: "Sorry,the call feature is not available yet. Please retry later.", preferredStyle: .Alert)
alertMessage.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
self.presentViewController(alertMessage, animated: true, completion: nil)
}
let callAction = UIAlertAction(title: "Call " + "123-000-\(indexPath.row)", style: UIAlertActionStyle.Default, handler: callActionHandler)
optionMenu.addAction(callAction)
直接使用闭包,填充UIAlertAction的handler
let isVisitedAction = UIAlertAction(title: "I've been here", style: .Default, handler: {
(action:UIAlertAction!) -> Void in
let cell = tableView.cellForRowAtIndexPath(indexPath)
cell?.accessoryType = .Checkmark
})
optionMenu.addAction(isVisitedAction)
3. 批量初始化一个数组
var restaurantIsVisited = [Bool](count: 21, repeatedValue: false)
4. 隐藏状态栏
override func prefersStatusBarHidden() -> Bool {
return true
}
info.plist文件中,View controller-based status bar appearance项设为YES,则View controller对status bar的设置优先级高于application的设置。为NO则以application的设置为准,view controller的prefersStatusBarHidden方法无效,是根本不会被调用的。
根据以上描述分以下两种情形:
一.View controller-based status bar appearance设为YES。
这时 view controller中对status bar的设置优先级高于application的设置,用下面的方式隐藏status bar。
分两步实现:
第一步:在view controller中调用setNeedsStatusBarAppearanceUpdate,更新status bar的显示
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if ([self respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]) {
[self prefersStatusBarHidden];
[self performSelector:@selector(setNeedsStatusBarAppearanceUpdate)];
}
}
第二步:覆盖view controller的prefersStatusBarHidden的实现,返会YES。
- (BOOL)prefersStatusBarHidden
{
return YES;
}
二.View controller-based status bar appearance设为NO
这时application的设置优先级最高,用下面的方式隐藏status bar:
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:NO];
结论
- 如果View controller-based status bar appearance 设为NO,iOS6和iOS7都是用下面的方法隐藏status bar。
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:NO];
- 如果View controller-based status bar appearance 设为YES,则需要判断当前是iOS6还是iOS7。
如果是iOS6,则还通过sharedApplication隐藏。
如果是iOS7,则用setNeedsStatusBarAppearanceUpdate加prefersStatusBarHidden的方式来隐藏 status bar。
取info.plist中 View controller-based status bar appearance中的设置
NSNumber *isVCBasedStatusBarAppearanceNum = [[NSBundle mainBundle]objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"];
if (isVCBasedStatusBarAppearanceNum) {
_isVCBasedStatusBarAppearance = isVCBasedStatusBarAppearanceNum.boolValue;
} else {
_isVCBasedStatusBarAppearance = YES; // default
}
参考链接:http://www.cnblogs.com/machenglong/p/3795876.html
5. UITableView
- UITableViewDataSource
tableView(_:numberOfRowsInSection:) 控制tableView中section中对应的行数(一个section有多少行)
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
// Return the number of rows in the section.
}
tableView(_:cellForRowAtIndexPath:) 定制tableView单元格样式,及填充数据
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
}
tableView(_:commitEditingStyle:forRowAtIndexPath:) 设置tableView可编辑
override func tableView(tableView: UITableView!, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath!) {
}
控制tableView的section,默认为0
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// Return the number of sections.
return 1
}
- UITableViewDataSource
6. UITableView Delete Row
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete the row from the data source
// self.restaurantNames.removeAtIndex(indexPath.row)
// self.restaurantLocations.removeAtIndex(indexPath.row)
// self.restaurantTypes.removeAtIndex(indexPath.row)
// self.restaurantIsVisited.removeAtIndex(indexPath.row)
// self.restaurantImages.removeAtIndex(indexPath.row)
//self.tableView.reloadData() 更新tableView,推荐使用后者的代码,具有动画效果
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
7. UITableViewRowAction (iOS8新特性)
覆盖tableView(_:editActionsForRowAtIndexPath:)方法
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject] {
var shareAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title:"Share", handler: { (action:UITableViewRowAction!, indexPath:NSIndexPath!) -> Void in
let shareMenu = UIAlertController(title: nil, message: "Share using",preferredStyle: .ActionSheet)
let twitterAction = UIAlertAction(title: "Twitter", style:UIAlertActionStyle.Default, handler: nil)
let facebookAction = UIAlertAction(title: "Facebook", style:UIAlertActionStyle.Default, handler: nil)
let emailAction = UIAlertAction(title: "Email", style: UIAlertActionStyle.Default,handler: nil)
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel,handler: nil)
shareMenu.addAction(twitterAction)
shareMenu.addAction(facebookAction)
shareMenu.addAction(emailAction)
shareMenu.addAction(cancelAction)
self.presentViewController(shareMenu, animated: true, completion: nil)
})
var deleteAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default,title: "Delete",handler: { (action:UITableViewRowAction!, indexPath:NSIndexPath!) -> Void in
// Delete the row from the data source
self.restaurantNames.removeAtIndex(indexPath.row)
self.restaurantLocations.removeAtIndex(indexPath.row)
self.restaurantTypes.removeAtIndex(indexPath.row)
self.restaurantIsVisited.removeAtIndex(indexPath.row)
self.restaurantImages.removeAtIndex(indexPath.row)
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
})
shareAction.backgroundColor = UIColor(red: 255.0/255.0, green: 166.0/255.0, blue:51.0/255.0, alpha: 1.0)
deleteAction.backgroundColor = UIColor(red: 51.0/255.0, green: 51.0/255.0, blue:51.0/255.0, alpha: 1.0)
return [deleteAction, shareAction]
}
8. prepareForSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "showRestaurantDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let destinationController = segue.destinationViewController as DetailViewController
destinationController.restaurantImage = self.restaurantImages[indexPath.row]
}
}
}
9. Customizing the Table View Appearance
- 修改tableView背景色
self.tableView.backgroundColor = UIColor(red: 240.0/255.0, green: 240.0/255.0, blue: 240.0/255.0, alpha: 0.2)
- 设置tableView单元格透明
在tableView(_:cellForRowAtIndexPath:)
中添加如下代码(设置单元格透明,使tableView背景色可见):
cell.backgroundColor = UIColor.clearColor()
- 移除tableView多余的分割线
_在viewDidLoad_方法中设置*
self.tableView.tableFooterView = UIView(frame: CGRectZero)
- 修改tableView分割线颜色
_在viewDidLoad_方法中设置*
self.tableView.separatorColor = UIColor(red: 240.0/255.0, green: 240.0/255.0, blue: 240.0/255.0,alpha: 0.8)
10. Customizing the Appearance of NavigationBar
- 修改导航栏背景色
UINavigationBar.appearance().barTintColor = UIColor(red: 231.0/255.0, green: 95.0/255.0, blue: 53.0/255.0, alpha: 0.3)
- 修改导航栏标题字体大小及颜色
UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName:UIColor.whiteColor(),NSFontAttributeName: UIFont(name: "AvenirNextCondensed-DemiBold",size: 22.0)]
iOS Font Name http://iosfonts.com/
- 修改导航栏返回按钮颜色
UINavigationBar.appearance().tintColor = UIColor.whiteColor()
- 修改导航栏返回按钮标题
override func viewDidLoad() {
super.viewDidLoad()
// Empty back button title
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .Plain, target: nil, action: nil)
}
完整的代码大致如下:
// 设置导航栏背景色
UINavigationBar.appearance().barTintColor = UIColor(red: 231.0/255.0, green: 95.0/255.0, blue: 53.0/255.0, alpha: 0.3)
// 设置导航栏按钮文字颜色
UINavigationBar.appearance().tintColor = UIColor.whiteColor()
// 设置导航栏标题字体大小及颜色
UINavigationBar.appearance().titleTextAttributes =
[NSForegroundColorAttributeName:UIColor.whiteColor(), NSFontAttributeName:UIFont(name:
"AvenirNextCondensed-DemiBold", size: 22.0)]
将以上代码添加到application(_:didFinishLaunchingWithOptions:)
方法中
- 修改导航栏标题
viewDidLoad方法中添加如下代码
title = self.restaurant.name
11. 收缩导航栏 (iOS8新特性)
在视图A中添加如下代码:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// 设置隐藏导航栏
self.navigationController?.hidesBarsOnSwipe = true
}
在视图B中添加如下代码:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// 设置不隐藏导航栏
self.navigationController?.hidesBarsOnSwipe = false
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
viewDidLoad
方法在视图可见或移除时调用。当视图可见时会调用viewWillAppear
和viewDidAppear
方法。viewWillAppear
方法在视图将要显示时调用,viewDidAppear
在视图已经显示可见后调用。viewWillAppear
方法在每次视图可见的时候都会调用。
12. Change the Style of Status Bar(修改状态栏样式)
- 方法一: 在每个视图中添加如下代码
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return .LightContent
}
- 方法二: 基于配置文件和编码
选择项目,在项目属性的info
选项中添加新的属性。key为View controller-based status bar appearance
value为NO
。这将影响整个项目
在AppDelegate
中的application(_:didFinishLaunchingWithOptions:)
方法中添加如下代码:
UIApplication.sharedApplication().statusBarStyle = .LightContent
参考链接:http://stackoverflow.com/questions/17678881/how-to-change-status-bar-text-color-in-ios-7
13. Self Sizing Cells (iOS8新特性)
在viewDidLoad
中添加如下代码:
tableView.estimatedRowHeight = 36.0;//与tableView的rowHeight相等
tableView.rowHeight = UITableViewAutomaticDimension;
注意:同时记得设置Cell中label的lines
属性的值为0
,默认为1
14. 连线Storyboard退出
@IBAction func close(segue:UIStoryboardSegue) {
}
将Storyboard中视图控制器上的Exit
图标与上面的代码关联就好。注意检测类型为unwind segue
15. 设置背景模糊
这里是通过给ViewController添加一个UIImageView控件,然后为UIImageView设置毛玻璃效果
在ViewController创建UIImageView的一个属性引用
@IBOutlet weak var backgroundImageView:UIImageView!
在ViewController的viewDidLoad
方法中添加如下代码
var blurEffect = UIBlurEffect(style: UIBlurEffectStyle.Dark)//模糊的样式
var blurEffectView = UIVisualEffectView(effect: blurEffect)//创建UIVisualEffectView
blurEffectView.frame = view.bounds//获取当前ViewController的view.bounds
backgroundImageView.addSubview(blurEffectView)//为backgroundImageView添加蒙板
16. Creating Round Buttons in Interface Builder
在Interface Builder中为UIButton设置圆角(选中UIButton,在属性面板的User Defined Runtime Attributes添加如下的配置)
layer.cornerRadius Number 30
Key Path为layer.cornerRadius
Type为Number
Value为30
17. 设置UIBarButtonItem的颜色及UIToolbar的背景色
在AppDelegate
的application(_:willFinishLaunchingWithOptions:)
方法中添加如下代码:
// 设置UIBarButtonItem的颜色
UIBarButtonItem.appearance().tintColor = UIColor(red: 235.0/255.0, green: 73.0/255.0, blue: 27.0/255.0, alpha: 1.0)
// 设置UIToolbar的背景色
UIToolbar.appearance().barTintColor = UIColor(red: 237.0/255.0, green: 240.0/255.0, blue: 243.0/255.0, alpha: 0.5)
18. Basic Animations Using UIView
为UIView中添加动画,主要是设置控件的transform
属性。
- CGAffineTransformMakeScale 缩放动画
首先在viewDidLoad
中为目标控件设置动画初始值,代码如下:
dialogView.transform = CGAffineTransformMakeScale(0.0, 0.0)
以上代码设置dialogView的transform为CGAffineTransformMakeScale(0.0, 0.0)
接着在viewDidAppear方法中设置该控件动画的结束值
override func viewDidAppear(animated: Bool) {
UIView.animateWithDuration(0.7, delay: 0.0, options: nil, animations: {
self.dialogView.transform = CGAffineTransformMakeScale(1, 1)
}, completion: nil)
}
以上代码表达的意思是设置dialogView等比放大一倍,整个动画持续(或耗时)0.7秒,不延时。
- Spring Animation (iOS 7)
上述的动画效果用Spring Animation
的代码如下(同样在viewDidAppear方法中):
UIView.animateWithDuration(0.7, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: nil, animations: {
self.dialogView.transform = CGAffineTransformMakeScale(1, 1)
}, completion: nil)
- Slide Up Animation (CGAffineTransformMakeTranslation(x, y)位移动画)
CGAffineTransformMakeTranslation(x, y)该类动画主要是通过修改控件x,y的坐标值,来达到动画效果
同样首先在viewDidLoad
方法中为目标控件设置一个动画状态值(同样是控件的transform属性)
dialogView.transform = CGAffineTransformMakeTranslation(0, 500)
接着在viewDidAppear
方法中设置该控件动画的结束值
override func viewDidAppear(animated: Bool) {
// Spring animation
UIView.animateWithDuration(0.7, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.5, options: nil, animations: {
self.dialogView.transform = CGAffineTransformMakeTranslation(0, 0)
}, completion: nil)
}
以上代码的意思是,移动dialogView到(0,0)点,整个动画过程耗时0.7秒,不延迟。在viewDidLoad
方法中为dialogView的transform
属性设置为CGAffineTransformMakeTranslation(0, 500),紧接着在viewDidAppear
方法中为dialogView的transform
属性设置为CGAffineTransformMakeTranslation(0, 0),由于viewDidLoad
方法在viewDidAppear
方法之前调用,一开始dialogView位于(0, 500),随后位移到(0, 0)坐标点,x轴不变,y轴由500缩小到0(垂直方向缩小),由此观察到dialogView是一个Slide Up的动画效果。
- Combining Two Transforms (动画合并)
顾名思义,就是为一个视图控件,同时绑定多个动画效果,主要通过使用CGAffineTransformConcat(transform1, transform2)
来实现。
CGAffineTransformConcat(transform1, transform2)
首先在viewDidLoad
方法中定义一个等比缩放动画的初始值及一个位移动画的初始值,代码如下:
let scale = CGAffineTransformMakeScale(0.0, 0.0)
let translate = CGAffineTransformMakeTranslation(0, 500)
dialogView.transform = CGAffineTransformConcat(scale, translate)
接着在viewDidAppear
方法中同样定义一个等比缩放动画的结束值及一个位移动画的结束值
UIView.animateWithDuration(0.7, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.5, options: nil, animations: {
let scale = CGAffineTransformMakeScale(1, 1)
let translate = CGAffineTransformMakeTranslation(0, 0)
self.dialogView.transform = CGAffineTransformConcat(scale, translate)
}, completion: nil)
上述代码表达的意思是:dialogView等比放大一倍,同时向上移动(y坐标从0改变到500),整个动画过程耗时0.7秒,不延时。通俗了讲就是:等比放大一倍,y坐标从 0 Slide Up 到 500。同理Slide Down为y坐标减小(比如:y坐标从0减小到-500)
在动画这里,需要了解视图的生命周期及与之对应的每一个方法:
ViewController的生命周期中各方法执行流程如下:
init—>loadView—>viewDidLoad—>viewWillApper—>viewDidApper—>viewWillDisapper—>viewDidDisapper—>viewWillUnload->viewDidUnload—>dealloc
注:loadView和viewDidLoad的区别就是,loadView时view还没有生成,viewDidLoad时,view已经生成了,loadView只会被调用一次,而viewDidLoad可能会被调用多次(View可能会被多次加载),当view被添加到其他view中之前,会调用viewWillAppear,之后会调用viewDidAppear。当view从其他view中移除之前,调用viewWillDisAppear,移除之后会调用viewDidDisappear。当view不再使用时,受到内存警告时,ViewController会将view释放并将其指向为nil。
19. MapView
首先添加MapKit framework
。选中项目的target
,在capabilities
选项卡下,开启Maps
为ON
即可。
- 在地图上添加标注
override func viewDidLoad() {
super.viewDidLoad()
// Convert address to coordinate and annotate it on map
let geoCoder = CLGeocoder()
geoCoder.geocodeAddressString(restaurant.location, completionHandler: { placemarks,
error in
if error != nil {
println(error)
return
}
if placemarks != nil && placemarks.count > 0 {
let placemark = placemarks[0] as CLPlacemark
// Add Annotation
let annotation = MKPointAnnotation()
annotation.title = self.restaurant.name
annotation.subtitle = self.restaurant.type
annotation.coordinate = placemark.location.coordinate
self.mapView.showAnnotations([annotation], animated: true)
self.mapView.selectAnnotation(annotation, animated: true)
}
})
}
- 为标注添加图片
首先实现MKMapViewDelegate
协议,接着重写mapView(_:viewForAnnotation:)
方法。记着在viewDidLoad
方法中为mapView
设置代理mapView.delegate = self;
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
let identifier = "MyPin"
if annotation.isKindOfClass(MKUserLocation) {
return nil
}
// Reuse the annotation if possible
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier)
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView.canShowCallout = true
}
let leftIconView = UIImageView(frame: CGRectMake(0, 0, 47, 47))
leftIconView.image = UIImage(named: restaurant.image)
annotationView.leftCalloutAccessoryView = leftIconView
return annotationView
}
20. Static Table View and UIImagePickerController
- Static Table View
首先拖一个UITableViewController
,然后在tableView
的属性栏中修改tableView
的Content
属性为Static Cells
。默认会创建三个静态空白的Cell。
- Displaying Photo Library Using UIImagePickerController
if UIImagePickerController.isSourceTypeAvailable(.PhotoLibrary) {
let imagePicker = UIImagePickerController()
imagePicker.allowsEditing = false
imagePicker.sourceType = .PhotoLibrary
self.presentViewController(imagePicker, animated: true, completion: nil)
}
注:若指定imagePicker的sourceType为.Camera,则为照相模式。
获取用户选择的照片,需要实现UIImagePickerControllerDelegate
协议,需要实现imagePickerController(_:didFinishPickingMediaWithInfo:)
方法。具体代码如下:
@IBOutlet weak var imageView:UIImageView!
func imagePickerController(picker: UIImagePickerController!, didFinishPickingImage image: UIImage!, editingInfo: [NSObject : AnyObject]!) {
imageView.image = image
imageView.contentMode = UIViewContentMode.ScaleAspectFill
imageView.clipsToBounds = true
dismissViewControllerAnimated(true, completion: nil)//dismiss image picker
}
注:记得在viewDidLoad
方法中为UIImagePickerController设置imagePicker.delegate = self
注意:此处有一个bug,之前设置了状态栏的文字及背景色会失效,此处需要修复,需要实现UINavigationControllerDelegate
协议,具体代码如下:
func navigationController(navigationController: UINavigationController!, willShowViewController viewController: UIViewController!, animated: Bool) {
UIApplication.sharedApplication().setStatusBarStyle(.LightContent, animated: false)
}
注:记得为navigationController设置代理
21. Core Data
1. 首先创建Data Model
。Core Data -> Data Model -> FoodPin.xcdatamodeld
2. Add Entity
(创建Entity
,Entity
与实体名称对应,比如说该示例中的Restaurant
)
3. Add attributes
(添加属性)
name String
type String
location String
image Binary Data
isVisited Boolean
注:以上二三步是创建Data Model,紧接着,第四部创建Data Object
4. Create Data Object
import Foundation
import CoreData
class Restaurant:NSManagedObject {
@NSManaged var name:String!
@NSManaged var type:String!
@NSManaged var location:String!
@NSManaged var image:NSData!
@NSManaged var isVisited:NSNumber!
}
注:在上面的Data Object
定义中Binary Data
使用NSData
类型,Boolean
使用NSNumber
来定义。当使用NSNumber
来表达Boolean
类型时,非零的值表示为true
,零为false
5. Working with Managed Objects
5.1. Get the managed object context from AppDelegate
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext
注:获取managedObjectContext
5.2. Create a managed object for the Restaurant entity
NSEntityDescription.insertNewObjectForEntityForName("Restaurant", inManagedObjectContext: managedObjectContext) as Restaurant
注:此处的Restaurant
为Entity
值
5.3. Use the context to save the new object into database
managedObjectContext.save(&e)
具体代码如下:
import CoreData
if let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext {
restaurant = NSEntityDescription.insertNewObjectForEntityForName("Restaurant", inManagedObjectContext: managedObjectContext) as Restaurant
restaurant.name = nameTextField.text
restaurant.type = typeTextField.text
restaurant.location = locationTextField.text
restaurant.image = UIImagePNGRepresentation(imageView.image)
restaurant.isVisited = isVisited.boolValue//此处与书中有出入
var e: NSError?
if managedObjectContext.save(&e) != true {
println("insert error: \(e!.localizedDescription)")
return
}
}
注:UIImagePNGRepresentation
,将Image转化为NSData
5.4. Fetching Data Using Core Data
- 简便的方法(在
viewWillAppear
方法中添加如下代码:)
if let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext {
let fetchRequest = NSFetchRequest(entityName: "Restaurant")
var e: NSError?
restaurants = managedObjectContext.executeFetchRequest(fetchRequest, error: &e) as [Restaurant]
if e != nil {
println("Failed to retrieve record: \(e!.localizedDescription)")
}
}
- 使用
NSFetchedResultsController
(实现NSFetchedResultsControllerDelegate
协议)
具体代码如下:
import CoreData
class RestaurantTableViewController:UITableViewController, NSFetchedResultsControllerDelegate {
var fetchResultController:NSFetchedResultsController!
override func viewDidLoad() {
super.viewDidLoad()
var fetchRequest = NSFetchRequest(entityName: "Restaurant")
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
if let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext {
fetchResultController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
fetchResultController.delegate = self
var e: NSError?
var result = fetchResultController.performFetch(&e)
restaurants = fetchResultController.fetchedObjects as [Restaurant]
if result != true {
println(e?.localizedDescription)
}
}
}
}
使用NSFetchedResultsController
,若内容发生改变时,将自动调用NSFetchedResultsControllerDelegate
的以下几个方法:
- controllerWillChangeContent(_:)
- controller(_:didChangeObject:atIndexPath:forChangeType:newIndexPath:)
- controllerDidChangeContent(_:)
其对应的调用顺序以上文的顺序自上而下依次调用。
controllerWillChangeContent(_:)
func controllerWillChangeContent(controller: NSFetchedResultsController!) {
tableView.beginUpdates()
}
controller(_:didChangeObject:atIndexPath:forChangeType:newIndexPath:)
func controller(controller: NSFetchedResultsController!, didChangeObject anObject: AnyObject!, atIndexPath indexPath: NSIndexPath!, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath!) {
switch type {
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)//注意:第一个参数为[newIndexPath]
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
case .Update:
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
default:
tableView.reloadData()
}
restaurants = controller.fetchedObjects as [Restaurant]
}
controllerDidChangeContent(_:)
func controllerDidChangeContent(controller: NSFetchedResultsController!) {
tableView.endUpdates()
}
5.5. Deleting Data Using Core Data
managedObjectContext.deleteObject(restaurantToDelete)
具体代码如下:
var deleteAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Delete",handler: {
(action:UITableViewRowAction!, indexPath:NSIndexPath!) -> Void in
// Delete the row from the data source
if let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext {
let restaurantToDelete = self.fetchResultController.objectAtIndexPath(indexPath) as Restaurant
managedObjectContext.deleteObject(restaurantToDelete)
var e: NSError?
if managedObjectContext.save(&e) != true {
println("delete error: \(e!.localizedDescription)")
}
}
})
6. Viewing the Raw SQL Statement
选择Stop
按钮右边的项目名称,选择Edit Scheme
,选择Arguments
选项卡,在Argument Passed on Launch
选项下添加如下参数:
-com.apple.CoreData.SQLDebug 1
点击OK后,再次运行即可在控制台看到真实的SQL输出。
22. Search Bar (UISearchController
iOS 8新特性)
在iOS 8中使用UISearchController
替换 UISearchDisplayController
。
- Using UISearchController
使用UISearchController的代码大致如下:
searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self//需要实现UISearchResultsUpdating协议
tableView.tableHeaderView = searchController.searchBar
definesPresentationContext = true
- Adding Search Bar
具体代码如下:
searchController = UISearchController(searchResultsController: nil)
searchController.searchBar.sizeToFit()
searchController.searchResultsUpdater = self//需要实现UISearchResultsUpdating协议
tableView.tableHeaderView = searchController.searchBar
definesPresentationContext = true
注:上述代码是为tableView添加searchBar(充当tableHeaderView)
- Filtering Content
var searchResults:[Restaurant] = []
func filterContentForSearchText(searchText: String) {
searchResults = restaurants.filter({ ( restaurant: Restaurant) -> Bool in
let nameMatch = restaurant.name.rangeOfString(searchText, options: NSStringCompareOptions.CaseInsensitiveSearch)
let locationMatch = restaurant.location.rangeOfString(searchText, options: NSStringCompareOptions.CaseInsensitiveSearch)
return nameMatch != nil || locationMatch != nil
})
}
注:以上代码是通过使用数组的filter方法来实现过滤
- Updating Search Results
实现UISearchResultsUpdating
协议。
在viewDidLoad
方法中添加以下代码:
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
重写updateSearchResultsForSearchController
方法,代码大致如下:
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchText = searchController.searchBar.text//获取检索字符
filterContentForSearchText(searchText)//内容过滤
tableView.reloadData()//更新tableView
}
- Customizing the Appearance of Search Bar(个性化定制Search Bar外观)
searchController.searchbar.tintColor //searchbar文字颜色
searchController.searchbar.placeholder //searchbar占位提示字符内容
searchController.searchbar.prompt //位于searchbar上方的文字内容
searchController.searchbar.barTintColor //searchbar背景色
23. UIPageViewController
UIPageViewController
提供vertical
和horizontal
两种样式,其过渡样式又分为Page Curl
和Scroll
两种,默认为Page Curl
首先在Storyboard
中拖入一个PageViewController
。(并为其指定Storyboard ID
为PageViewController
),事实上PageViewController
为一个容器,用来控制和显示具体的PageView
。一般将这个视图称为PageContentViewController
,在该视图上设计要显示的内容。同样在在Storyboard
中拖入一个View Controller
设置其Storyboard ID为PageContentViewController
。拖入两个label和一个imageview填充PageContentViewController
,作为PageViewController
要控制显示的视图。
- 创建
PageContentViewController
class继承UIViewController
,并将UI与代码关联。其代码大致如下:
@IBOutlet weak var headingLabel:UILabel!//大标题
@IBOutlet weak var subHeadingLabel:UILabel!//二级标题
@IBOutlet weak var contentImageView:UIImageView!//图片
var index : Int = 0 //索引,标识当前PageContentViewController的索引
var heading : String = ""
var imageFile : String = ""
var subHeading : String = ""
override func viewDidLoad() {
super.viewDidLoad()
headingLabel.text = heading
subHeadingLabel.text = subHeading
contentImageView.image = UIImage(named: imageFile)
}
- 创建
PageViewController
class继承UIPageViewController
,并且实现UIPageViewControllerDataSource
协议。其代码大致如下:
PageViewController
继承UIPageViewController
并且实现UIPageViewControllerDataSource
协议,该类主要控制及显示具体的PageContentView
(PageContentViewController
),通过UIPageViewControllerDataSource
协议中的两个方法来控制其显示。
pageViewController(_:viewControllerBeforeViewController:)//上一个PageContentView
pageViewController(_:viewControllerAfterViewController:)//下一个PageContentView
class PageViewController: UIPageViewController, UIPageViewControllerDataSource{
var pageHeadings = ["Personalize", "Locate", "Discover"]//大标题
var pageImages = ["homei", "mapintro", "fiveleaves"]//图片名称
var pageSubHeadings = ["Pin your favourite restaurants and create your own food guide", "Search and locate your favourite restaurant on Maps", "Find restaurants pinned by your friends and other foodies around the world"]//二级标题
override func viewDidLoad() {
super.viewDidLoad()
// Set the data source to itself
dataSource = self
// Create the first walkthrough screen 创建第一个PageContentView
if let startingViewController = self.viewControllerAtIndex(0) {
setViewControllers([startingViewController], direction: .Forward, animated: true, completion: nil)
}
}
//下一个PageContentView
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
var index = (viewController as PageContentViewController).index
index++
return self.viewControllerAtIndex(index)
}
//上一个PageContentView
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
var index = (viewController as PageContentViewController).index
index--
return self.viewControllerAtIndex(index)
}
//控制PageContentView的轮询切换
func viewControllerAtIndex(index: Int) -> PageContentViewController? {
if index == NSNotFound || index < 0 || index >= self.pageHeadings.count {
return nil
}
// Create a new view controller and pass suitable data.
if let pageContentViewController =
storyboard?.instantiateViewControllerWithIdentifier("PageContentViewController") as? PageContentViewController {
pageContentViewController.imageFile = pageImages[index]
pageContentViewController.heading = pageHeadings[index]
pageContentViewController.subHeading = pageSubHeadings[index]
pageContentViewController.index = index
return pageContentViewController
}
return nil
}
/*************************************默认的Page Indicator*********************************************/
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return pageHeadings.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
if let pageContentViewController = storyboard?.instantiateViewControllerWithIdentifier("PageContentViewController") as? PageContentViewController {
return pageContentViewController.index
}
return 0
}
}
注:类PageViewController
创建和控制PageContentView
,storyboard?.instantiateViewControllerWithIdentifier("PageContentViewController") as? PageContentViewController
,使用storyboard
通过在storyboard
中为ViewController设置的Storyboard ID
获取ViewController(PageContentViewController)
- Display(使用)
if let pageViewController = storyboard?.instantiateViewControllerWithIdentifier("PageViewController") as? PageViewController {
self.presentViewController(pageViewController, animated: true, completion: nil)
}
通过storyboard?.instantiateViewControllerWithIdentifier("PageViewController") as? PageViewController
获取PageViewController
(PageView控制器),最终使用presentViewController
显示。
- 添加默认的Page Indicator
通过在PageViewController
中实现UIPageViewControllerDataSource
中的以下两个方法实现:
presentationCountForPageViewController PageContentView总个数
presentationIndexForPageViewController 当前选中的PageContentView的索引
其代码大致如下:
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return pageHeadings.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
if let pageContentViewController = storyboard?.instantiateViewControllerWithIdentifier("PageContentViewController") as? PageContentViewController {
return pageContentViewController.index
}
return 0
}
注:dismissViewControllerAnimated(true, completion: nil)
关闭或销毁当前ViewController
- Custom Page Indicator(自定义Page Indicator)
通过UIPageControl
控件实现。(略)
- NSUserDefaults
使用NSUserDefaults.standardUserDefaults()
获取NSUserDefaults
对象。
通过下面的方法检索值。
arrayForKey(_:)
boolForKey(_:)
dataForKey(_:)
dictionaryForKey(_:)
floatForKey(_:)
integerForKey(_:)
objectForKey(_:)
stringArrayForKey(_:)
stringForKey(_:)
doubleForKey(_:)
URLForKey(_:)
大致代码如下:
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setBool(true, forKey: "hasViewedWalkthrough")//存放一个Boolean值,其值为true,键为hasViewedWalkthrough
24. Tab Bar
select the Navigation Controller(Initial View Controller) -> select Editor > Embed in > Tab Bar Controller.
- Hide Tab Bar When Pushed
在使用Navigation Controller push后的ViewController中隐藏Tab Bar有以下两种方法:
第一种方法:
在StoryBoard
中选择目标ViewController在Attribute Inspector
选项中勾选Hide Bottom Bar on Push
。
第二种方法:
在prepareForSegue
方法中设置destinationController
的hideBottomBarWhenPushed
属性为true
destinationController.hideBottomBarWhenPushed = true
- Customizing the Appearance of Tab Bar
tintColor 文字颜色
UITabBar.appearance().tintColor = UIColor(red: 235.0/255.0, green: 75.0/255.0, blue: 27.0/255.0, alpha: 1.0)
barTintColor 背景色
UITabBar.appearance().barTintColor = UIColor.blackColor()
Tab Bar Item Image
修改TabBar选项卡的图片,选中该选项,在Attribute Inspector
选项中修改system item
选项为Custom
然后设置Title
或Image
属性。
Selection Indicator Image 设置选中后的图片
UITabBar.appearance().selectionIndicatorImage = UIImage(named: "tabitem_selected")
25. WebView and Email
- Loading Web Content Using UIWebView
let url = NSURL(string: "http://www.appcoda.com")
//let url = NSURL(fileURLWithPath: "about.html")
let request = NSURLRequest(URL: url)
webView.loadRequest(request)
- MFMailComposeViewController
使用MFMailComposeViewController
发送邮件,实现MFMailComposeViewControllerDelegate
协议中的mailComposeController(_:didFinishWithResult:error:)
方法
import MessageUI
class AboutViewController: UIViewController, MFMailComposeViewControllerDelegate, UINavigationControllerDelegate {
// 点击后触发写邮件界面
@IBAction func sendEmail (sender: AnyObject) {
if MFMailComposeViewController.canSendMail() {
var composer = MFMailComposeViewController()
composer.mailComposeDelegate = self
composer.setToRecipients(["[email protected]"])
composer.navigationBar.tintColor = UIColor.whiteColor()
presentViewController(composer, animated: true, completion: {
UIApplication.sharedApplication().setStatusBarStyle(.LightContent, animated: false)
})
}
}
func mailComposeController(controller: MFMailComposeViewController!, didFinishWithResult result: MFMailComposeResult, error: NSError!) {
switch result.value {
case MFMailComposeResultCancelled.value:
println("Mail cancelled")
case MFMailComposeResultSaved.value:
println("Mail saved")
case MFMailComposeResultSent.value:
println("Mail sent")
case MFMailComposeResultFailed.value:
println("Failed to send mail: \(error.localizedDescription)")
default:
break
}
// Dismiss the Mail interface
dismissViewControllerAnimated(true, completion: nil)
}
}
26. CloudKit
- Enabling CloudKit in Your App(启用CloudKit)
Targets -> Capabilities -> iCloud -> ON -> CloudKit
选择Targets,切换到Capabilities选项卡,在iCloud选项上选择ON,并且选择iCloud选项下方的Services属性为CloudKit
- Managing Your Record in CloudKit Dashboard
使用CloudKit Dashboard
来管理和创建(具体与CoreData的用法类似)
在左侧的面板区域,选择Record Types
,点右边的+
创建一个Record Type
(如:Restaurant),接着定义attribute。CloudKit
支持String,Data/Time,Double,Location,Asset(存放图片)等类型。
本书例子中的属性定义对应如下:
name String
type String
location String
image Asset
定义好属性后,可以使用面板上的+
添加数据。
- Fetching Data from Public Database Using Convenience API
大致代码如下所示:
let cloudContainer = CKContainer.defaultContainer()
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Restaurant", predicate: predicate) //Restaurant为在iCloud中创建的Record Type
publicDatabase.performQuery(query, inZoneWithID: nil, completionHandler: { results, error in
// Process the records
})
具体代码片段:
import CloudKit
var restaurants:[CKRecord] = []
self.getRecordsFromCloud()
func getRecordsFromCloud() {
// Fetch data using Convenience API
let cloudContainer = CKContainer.defaultContainer()
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Restaurant", predicate: predicate) //Restaurant为在iCloud中创建的Record Type
publicDatabase.performQuery(query, inZoneWithID: nil, completionHandler: { results, error in
if error == nil {
println("Completed the download of Restaurant data")
self.restaurants = results as [CKRecord] //将结果转化为[CKRecord]
//self.tableView.reloadData() //更新tableView数据源
// 使用dispatch_async优化代码。在主线程中异步更新tableView数据源
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData() //更新tableView数据源
})
} else {
println(error)
}
})
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
return restaurants.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
// Configure the cell...
let restaurant = restaurants[indexPath.row]
cell.textLabel.text = restaurant.objectForKey("name") as? String //获取iCloud中创建的属性为name值
if (restaurant.objectForKey("image") != nil) {
let imageAsset = restaurant.objectForKey("image") as CKAsset //获取iCloud中创建的属性为image值并转化为CKAsset
cell.imageView?.image = UIImage(data: NSData(contentsOfURL: imageAsset.fileURL))
}
return cell
}
- Fetching Data from Public Database Using Operational API
替换getRecordsFromCloud方法:
func getRecordsFromCloud() {
// Initialize an empty restaurants array
restaurants = []
// Get the Public iCloud Database
let cloudContainer = CKContainer.defaultContainer()
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
// Prepare the query
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Restaurant", predicate: predicate)
// Create the query operation with the query
let queryOperation = CKQueryOperation(query: query)
queryOperation.desiredKeys = ["name", "image"]
queryOperation.queuePriority = .VeryHigh
queryOperation.resultsLimit = 50
queryOperation.recordFetchedBlock = { (record:CKRecord!) -> Void in
if let restaurantRecord = record {
self.restaurants.append(restaurantRecord)
}
}
queryOperation.queryCompletionBlock = { (cursor:CKQueryCursor!, error:NSError!) -> Void in
if (error != nil) {
println("Failed to get data from iCloud - \(error.localizedDescription)")
} else {
println("Successfully retrieve the data from iCloud")
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}
}
// Execute the query
publicDatabase.addOperation(queryOperation)
}
- Activity Indicator (UIActivityIndicatorView)
显示UIActivityIndicatorView
var spinner:UIActivityIndicatorView = UIActivityIndicatorView()
spinner.activityIndicatorViewStyle = .Gray //设置样式为Gray
spinner.center = self.view.center //局中显示
spinner.hidesWhenStopped = true //设置停止的时候可隐藏
self.parentViewController?.view.addSubview(spinner) //添加到父视图控制器中
spinner.startAnimating() //显示UIActivityIndicatorView
UIActivityIndicatorView
有三种样式:Gray
, White (default)
和 WhiteLarge
隐藏或关闭UIActivityIndicatorView
dispatch_async(dispatch_get_main_queue(), {
self.spinner.stopAnimating() //在主线程中调用stopAnimating()隐藏或关闭UIActivityIndicatorView
})
- Lazy Loading Images(Image懒加载)
修改上文中的getRecordsFromCloud
方法
queryOperation.desiredKeys = ["name", "image"]
//修改为
queryOperation.desiredKeys = ["name"]
修改上文中的tableView(_:cellForRowAtIndexPath:)
方法
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
if restaurants.isEmpty {
return cell
}
// Configure the cell...
let restaurant = restaurants[indexPath.row]
cell.textLabel.text = restaurant.objectForKey("name") as? String
// Set default image
cell.imageView.image = UIImage(named: "camera")
// Fetch Image from Cloud in background
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
let fetchRecordsImageOperation = CKFetchRecordsOperation(recordIDs: [restaurant.recordID])
fetchRecordsImageOperation.desiredKeys = ["image"]
fetchRecordsImageOperation.queuePriority = .VeryHigh
fetchRecordsImageOperation.perRecordCompletionBlock = {(record:CKRecord!, recordID:CKRecordID!, error:NSError!) -> Void in
if (error != nil) {
println("Failed to get restaurant image: \(error.localizedDescription)")
} else {
if let restaurantRecord = record {
dispatch_async(dispatch_get_main_queue(), { //后台异步加载image
let imageAsset = restaurantRecord.objectForKey("image") as CKAsset
cell.imageView.image = UIImage(data: NSData(contentsOfURL: imageAsset.fileURL)!)
})
}
}
}
publicDatabase.addOperation(fetchRecordsImageOperation)
return cell
}
- Caching Images Using NSCache(使用缓存缓存Image)
var imageCache:NSCache = NSCache()
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
if restaurants.isEmpty {
return cell
}
// Configure the cell...
let restaurant = restaurants[indexPath.row]
cell.textLabel.text = restaurant.objectForKey("name") as? String
// Set default image
cell.imageView.image = UIImage(named: "camera")
// See if we can get the image from cache 检测Cache中是否存在image
if let imageFileURL = imageCache.objectForKey(restaurant.recordID) as? NSURL {
println("Get image from cache")
cell.imageView.image = UIImage(data: NSData(contentsOfURL: imageFileURL)!)
} else {
// Fetch Image from Cloud in background
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
let fetchRecordsImageOperation = CKFetchRecordsOperation(recordIDs: [restaurant.recordID])
fetchRecordsImageOperation.desiredKeys = ["image"]
fetchRecordsImageOperation.queuePriority = .VeryHigh
fetchRecordsImageOperation.perRecordCompletionBlock = {(record:CKRecord!, recordID:CKRecordID!, error:NSError!) -> Void in
if (error != nil) {
println("Failed to get restaurant image: \(error.localizedDescription)")
} else {
if let restaurantRecord = record {
dispatch_async(dispatch_get_main_queue(), {
let imageAsset = restaurantRecord.objectForKey("image") as CKAsset
self.imageCache.setObject(imageAsset.fileURL, forKey: restaurant.recordID)
cell.imageView.image = UIImage(data: NSData(contentsOfURL: imageAsset.fileURL)!)
})
}
}
}
publicDatabase.addOperation(fetchRecordsImageOperation)
}
return cell
}
- Pull to Refresh(下拉刷新)
在TableViewController
的viewDidLoad
方法中添加如下代码:
// Pull To Refresh Control
refreshControl = UIRefreshControl()
refreshControl?.backgroundColor = UIColor.whiteColor()
refreshControl?.tintColor = UIColor.grayColor()
refreshControl?.addTarget(self, action: "getRecordsFromCloud", forControlEvents: UIControlEvents.ValueChanged)
使用下面的代码隐藏refresh control
// Hide the refresh control
self.refreshControl?.endRefreshing()
- Saving Data Using CloudKit(使用CloudKit保存数据到iCloud中)
func saveRecord(_ record: CKRecord!, completionHandler completionHandler: ((CKRecord!, NSError!) -> Void)!)
func saveRecordToCloud(restaurant:Restaurant!) -> Void {
// Prepare the record to save
var record = CKRecord(recordType: "Restaurant")
record.setValue(restaurant.name, forKey: "name")
record.setValue(restaurant.type, forKey: "type")
record.setValue(restaurant.location, forKey: "location")
// Resize the image
var originalImage = UIImage(data: restaurant.image)
var scalingFactor = (originalImage!.size.width > 1024) ? 1024 / originalImage!.size.width : 1.0
var scaledImage = UIImage(data: restaurant.image, scale: scalingFactor)
// Write the image to local file for temporary use
let imageFilePath = NSTemporaryDirectory() + restaurant.name
UIImageJPEGRepresentation(scaledImage, 0.8).writeToFile(imageFilePath, atomically: true)
// Create image asset for upload
let imageFileURL = NSURL(fileURLWithPath: imageFilePath)
var imageAsset = CKAsset(fileURL: imageFileURL)
record.setValue(imageAsset, forKey: "image")
// Get the Public iCloud Database
let cloudContainer = CKContainer.defaultContainer()
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
// Save the record to iCloud
public Database.saveRecord(record, completionHandler: { (record:CKRecord!, error:NSError!) -> Void in
// Remove temp file
NSFileManager.defaultManager().removeItemAtPath(imageFilePath, error: nil)
if (error != nil) {
println("Failed to save record to the cloud: \(error.description)")
}
})
}
- Sorting the Result by Creation Date
在CloudKit dashboard
中,在meta index
,选择Fields
选项,为Date Created
meta data 选项勾选Sort
。
在getRecordsFromCloud
方法中添加如下代码:
// Prepare the query
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Restaurant", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
27. Localization(国际化)
通过使用NSLocalizedString
宏来实现国际化,Xcode将国际化资源存储在Localizable.strings
文件中。
NSLocalizedString("Share using", comment: "For social sharing")
- Export for Localization
select Editor > Export For Localization(最终导出为XLIFF文件,XLIFF是一种标准格式的xml文件)
28. APPENDIX
28.1. Swift Basics
Objective-C
const int count = 10;
double price = 23.55;
NSString *myMessage = @"Objective-C is not dead yet!";
NSString *firstMessage = @"Swift is awesome. ";
NSString *secondMessage = @"What do you think?";
NSString *message = [NSString stringWithFormat:@"%@%@", firstMessage, secondMessage];
NSLog(@"%@", message);
Swift
let count = 10
var price = 23.55
//var myMessage = "Swift is the future!"
var myMessage : String = "Swift is the future!"
let dontModifyMe = "You cannot modify this string"
var modifyMe = "You can modify this string"
let firstMessage = "Swift is awesome. "
let secondMessage= "What do you think?"
var message = firstMessage + secondMessage
println(message)
var string1 = "Hello"
var string2 = "Hello"
if string1 == string2 {
println("Both are the same")
}
28.2. Arrays
Objective-C:
NSArray *recipes = @[@"Egg Benedict", @"Mushroom Risotto", @"Full Breakfast", @"Hamburger", @"Ham and Egg Sandwich"];
Swift:
//var recipes = ["Egg Benedict", "Mushroom Risotto", "Full Breakfast", "Hamburger", "Ham and Egg Sandwich"]
var recipes : String[] = ["Egg Benedict", "Mushroom Risotto", "Full Breakfast", "Hamburger","Ham and Egg Sandwich"]
var numberOfItems = recipes.count
recipes += "Thai Shrimp Cake"
recipes += ["Creme Brelee", "White Chocolate Donut", "Ham and Cheese Panini"]
var recipeItem = recipes[0]
recipes[1] = "Cupcake"
recipes[1...3] = ["Cheese Cake", "Greek Salad", "Braised Beef Cheeks"]
28.3. Dictionaries
Objective-C:
NSDictionary *companies = @{@"AAPL" : @"Apple Inc", @"GOOG" : @"Google Inc", @"AMZN" : @"Amazon.com, Inc", @"FB" : @"Facebook Inc"};
Swift:
var companies = ["AAPL" : "Apple Inc", "GOOG" : "Google Inc", "AMZN" : "Amazon.com, Inc", "FB" : "Facebook Inc"]
//var companies: Dictionary<String, String> = ["AAPL" : "Apple Inc", "GOOG" : "Google Inc", "AMZN" : "Amazon.com, Inc", "FB" : "Facebook Inc"]
for (stockCode, name) in companies {
println("\(stockCode) = \(name)")
}
for stockCode in companies.keys {
println("Stock code = \(stockCode)")
}
for name in companies.values {
println("Company name = \(name)")
}
companies["TWTR"] = "Twitter Inc"
28.4. Classes
class Recipe {
var name: String = ""
var duration: Int = 10
var ingredients: String[] = ["egg"]
}
class Recipe {
var name: String?
var duration: Int = 10
var ingredients: String[]?
}
var recipeItem = Recipe()
recipeItem.name = "Mushroom Risotto"
recipeItem.duration = 30
recipeItem.ingredients = ["1 tbsp dried porcini mushrooms", "2 tbsp olive oil", "1 onion, chopped", "2 garlic cloves", "350g/12oz arborio rice", "1.2 litres/2 pints hot vegetable stock", "salt and pepper", "25g/1oz butter"]
Objective-C:
@interface SimpleTableViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
Swift:
class SimpleTableViewController : UIViewController, UITableViewDelegate, UITableViewDataSource
28.5. Methods
class TodoManager {
func printWelcomeMessage() {
println("Welcome to My ToDo List")
}
func printWelcomeMessage(name:String) -> Int {
println("Welcome to \(name)'s ToDo List")
return 10
}
}
方法调用:
Objective-C:
TodoManager todoManager = [[TodoManager alloc] init]
[todoManager printWelcomeMessage];
Swift:
var todoManager = TodoManager()
todoManager.printWelcomeMessage()
let numberOfTodoItem = todoManager.printWelcomeMessage("Simon")
println(numberOfTodoItem)
28.6. Control Flow
- for loops
for i in 0..<5 {
println("index = \(i)")
}
输出结果:
index = 0
index = 1
index = 2
index = 3
index = 4
for i in 0...<5 {
println("index = \(i)")
}
输出结果:
index = 0
index = 1
index = 2
index = 3
index = 4
index = 5
注:..
不包含后者,...
包含后者。
for var i = 0; i < 5; i++ {
println("index = \(i)")
}
- if-else statement
var bookPrice = 1000;
if bookPrice >= 999 {
println("Hey, the book is expensive")
} else {
println("Okay, I can affort it")
}
- switch statement
switch recipeName {
case "Egg Benedict":
println("Let's cook!")
case "Mushroom Risotto":
println("Hmm... let me think about it")
case "Hamburger":
println("Love it!")
default:
println("Anything else")
}
var speed = 50
switch speed {
case 0:
println("stop")
case 0...40:
println("slow")
case 41...70:
println("normal")
case 71..<101:
println("fast")
default:
println("not classified yet")
}
28.7. Tuples
let company = ("AAPL", "Apple Inc", 93.5)
let (stockCode, companyName, stockPrice) = company
println("stock code = \(stockCode)")
println("company name = \(companyName)")
println("stock price = \(stockPrice)")
let product = (id: "AP234", name: "iPhone 6", price: 599)
println("id = \(product.id)")
println("name = \(product.name)")
println("price = USD\(product.price)")
class Store {
func getProduct(number: Int) -> (id: String, name: String, price: Int) {
var id = "IP435", name = "iMac", price = 1399
switch number {
case 1:
id = "AP234"
name = "iPhone 6"
price = 599
case 2:
id = "PE645"
name = "iPad Air"
price = 499
default:
break
}
return (id, name, price)
}
}
let store = Store()
let product = store.getProduct(2)
println("id = \(product.id)")
println("name = \(product.name)")
println("price = USD\(product.price)")
28.8. Optionals
var message: String = "Swift is awesome!" // OK
message = nil // compile-time error
class Messenger {
var message1: String = "Swift is awesome!" // OK
var message2: String // compile-time error
}
class Messenger {
var message1: String = "Swift is awesome!" // OK
var message2: String? // OK
}
func findStockCode(company: String) -> String? {
if (company == "Apple") {
return "AAPL"
} else if (company == "Google") {
return "GOOG"
}
return nil
}
var stockCode:String? = findStockCode("Facebook")
let text = "Stock Code - "
let message = text + stockCode // compile-time error
println(message)
28.9. Unwrapping Optionals
var stockCode:String? = findStockCode("Facebook")
let text = "Stock Code - "
if stockCode != nil {
let message = text + stockCode!
println(message)
}
var stockCode:String? = findStockCode("Facebook")
let text = "Stock Code - "
let message = text + stockCode! // runtime error
28.10. Optional Binding
var stockCode:String? = findStockCode("Facebook")
let text = "Stock Code - "
if let tempStockCode = stockCode {
let message = text + tempStockCode
println(message)
}
let text = "Stock Code - "
if var stockCode = findStockCode("Apple") {
let message = text + stockCode
println(message)
}
28.11. Optional Chaining
class Stock {
var code: String?
var price: Double?
}
func findStockCode(company: String) -> Stock? {
if (company == "Apple") {
let aapl: Stock = Stock()
aapl.code = "AAPL"
aapl.price = 90.32
return aapl
} else if (company == "Google") {
let goog: Stock = Stock()
goog.code = "GOOG"
goog.price = 556.36
return goog
}
return nil
}
if let stock = findStockCode("Apple") {
if let sharePrice = stock.price {
let totalCost = sharePrice * 100
println(totalCost)
}
}
if let sharePrice = findStockCode("Apple")?.price {
let totalCost = sharePrice * 100
println(totalCost)
}
哈哈,哥,真好,我自己买了这本书的电子版,纯英文的,看不懂,想问问你autolayout这个怎么搞,我的swift App图片显示的时候iPhone5s上显示满屏,但是到6的时候就显示没有铺满屏幕,这个我还是不知道怎么去解决!
@saraOrg 添加左右上下的 约束
哥 给个图片吧 我看不懂!
GOOD!!!!
well done
帅哥,这书能出售份给我吗?
你好 我在实践原书案例第11章内容时,遇到一个闪退的问题,详细的问题描述已经mail给你,能不能帮我看一下?谢谢!
问题已经解决 http://www.swiftmi.com/topic/242.html
哥,webview的加载进度怎么去模拟,我现在用webview去显示网页,要加个进度条,然后在网页加载的时候 去不断用进度条展示网页加载的进度 类似微博app打开链接的效果,不知道这个你没有什么好的思路给兄弟推荐一下!