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)
}```

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容