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 appearancevalue为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要控制显示的视图。

创建PageContentViewControllerclass继承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)
}```
创建PageViewControllerclass继承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(["support@appcoda.com"])
        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)
}```

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容