Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Beginning iOS 8 Programming with Swift 读书笔记 #26

Open
v5tech opened this issue Oct 30, 2014 · 8 comments
Open

Beginning iOS 8 Programming with Swift 读书笔记 #26

v5tech opened this issue Oct 30, 2014 · 8 comments

Comments

@v5tech
Copy link
Owner

v5tech commented Oct 30, 2014

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方法在视图可见或移除时调用。当视图可见时会调用viewWillAppearviewDidAppear方法。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的背景色

AppDelegateapplication(_: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选项卡下,开启MapsON即可。

  • 在地图上添加标注
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的属性栏中修改tableViewContent属性为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(创建EntityEntity与实体名称对应,比如说该示例中的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

注:此处的RestaurantEntity

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提供verticalhorizontal两种样式,其过渡样式又分为Page CurlScroll两种,默认为Page Curl

首先在Storyboard中拖入一个PageViewController。(并为其指定Storyboard IDPageViewController),事实上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创建和控制PageContentViewstoryboard?.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方法中设置destinationControllerhideBottomBarWhenPushed属性为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然后设置TitleImage属性。

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有三种样式:GrayWhite (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(下拉刷新)

TableViewControllerviewDidLoad方法中添加如下代码:

// 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)
}
@saraOrg
Copy link

saraOrg commented Nov 26, 2014

哈哈,哥,真好,我自己买了这本书的电子版,纯英文的,看不懂,想问问你autolayout这个怎么搞,我的swift App图片显示的时候iPhone5s上显示满屏,但是到6的时候就显示没有铺满屏幕,这个我还是不知道怎么去解决!

@androidcn
Copy link

@saraOrg 添加左右上下的 约束

@saraOrg
Copy link

saraOrg commented Nov 26, 2014

哥 给个图片吧 我看不懂!

@magicdict
Copy link

GOOD!!!!

@linKnowEasy
Copy link

well done

@SatanWoo
Copy link

帅哥,这书能出售份给我吗?

@TangSirOnGit
Copy link

你好 我在实践原书案例第11章内容时,遇到一个闪退的问题,详细的问题描述已经mail给你,能不能帮我看一下?谢谢!

问题已经解决
http://www.swiftmi.com/topic/242.html

@saraOrg
Copy link

saraOrg commented Mar 16, 2015

哥,webview的加载进度怎么去模拟,我现在用webview去显示网页,要加个进度条,然后在网页加载的时候 去不断用进度条展示网页加载的进度 类似微博app打开链接的效果,不知道这个你没有什么好的思路给兄弟推荐一下!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants