2014年7月25日 星期五

The variable set readonly of Swift language

【說明】

在Object-C下我們會這樣設定:

@property(nonatomic, assign, readonly)int score;
目的就是讓實體變數無法存任何值進去,只能讀取,如果在程式碼內撰寫存值的動作會出現以下的錯誤。

Object-C存值錯誤
在Swift語言我們可以這樣子宣告:

var score:Int {
    return (chinese+english+math)/3

}
此時score只能做讀取的動作,並無法做設定值的動作,如果在程式碼內撰寫存值的動作會出現以下的錯誤。

Swift存值錯誤

2014年7月15日 星期二

Call Object-C file from Swift

【說明】

此筆記記錄如何用Swift語言去呼叫Object-C檔案內的物件。

此專案在View上面建立一個UILabel,UILabel的文字從自訂的Object-C Class內取得。

【專案開發步驟】

設計使用者介面:

建立一個Single View Application模板的專案,名為CallObjectCFromSwift,並在View上面增加一個UILabel,且建立IBOutlet,如下圖所示。

設計使用者介面
新增Object-C的Class:

點選file -> New -> File...,如下圖所示。

新增檔案
新增一個Object-C File,名稱為Custom,如下圖所示。

新增Object-C File
建立完後會出現訊息詢問是否想要配置一個Object-C header的橋接,如下圖所示,此時點選Yes。

提示訊息
選擇Yes後會建立一個橋接的檔案,如下圖所示。

橋接的檔案
再新增一個檔案,選擇Header File,名稱與.m檔相同Custom,如下圖所示。

新增Header File
修改Custom.h與Custom.m檔案:

Custom.h:

//
//  Custom.h
//  CallObjectCFromSwift
//
//  Created by Hsu,Yi-Sheng on 2014/7/15.
//  Copyright (c) 2014 yisheng. All rights reserved.
//

#import <UIKit/UIKit.h>


@interface Custom : NSObject

@property(nonatomic, strong) NSString *string;

- (void) setCustomString;

@end

Custom.m:

//
//  Custom.m
//  CallObjectCFromSwift
//
//  Created by Hsu,Yi-Sheng on 2014/7/15.
//  Copyright (c) 2014 yisheng. All rights reserved.
//

#import "Custom.h"

@implementation Custom

- (void) setCustomString {
    self.string = @"Hello";
}

@end

在橋接檔案內新增Custom Class的import:

如下圖所示,新增Custom.h的import。

import Custom.h
修改ViewController.swift檔案:

//
//  ViewController.swift
//  CallObjectCFromSwift
//
//  Created by Hsu,Yi-Sheng on 2014/7/15.
//  Copyright (c) 2014 yisheng. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
    
    @IBOutlet var label: UILabel
                            
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        var customClass:Custom = Custom()
        customClass.setCustomString()
        
        label.text = customClass.string
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}
ViewDidLoad內建立一個名為customClass的變數,資料型態為Custom。利用customClass取得Custom Class內的function。將label的文字設定為customClass的string。


【執行結果】


2014年7月13日 星期日

Video Capture programmatically using Swift language

【說明】

此份筆記是根據ISBN:978-986-201-900-9這本書的第24章節所紀錄的。

此份專案可以讓使用者錄製一段影片,並在MPMoviePlayerController上顯示,並在影片播畢後顯示UIAlertView提醒使用者。


【專案開發步驟】

建立專案:

使用Single View Application模板建立一個名為VideoApp的專案,使用iPhone裝置。

設計使用者介面:

在畫面上加入一個按鈕,如下圖所示。

設計User Interface
建立物件與類別的IBAction,如下圖所示。

物件與類別的IBAction
實作VideoViewController:

首先在ViewController內import兩個標頭檔,如下所示。

import MediaPlayer
import MobileCoreServices

讓ViewController繼承UIImagePickerControllerDelegate與UINavigationControllerDelegate,如下所示。

class ViewController: UIViewControllerUIImagePickerControllerDelegateUINavigationControllerDelegate { }

建立兩個變數,分別如下。

var videoURL: NSURL!
var videoController: MPMoviePlayerController!

實作按鈕按下後的func:

將以下程式碼加入至func內。

if (UIImagePickerController.isSourceTypeAvailable(.Camera)) {
            
    var picker: UIImagePickerController = UIImagePickerController()
    picker.delegate = self
    picker.allowsEditing = true
    picker.sourceType = .Camera
    picker.mediaTypes = UIImagePickerController.availableMediaTypesForSourceType(.Camera)
    picker.videoQuality = .TypeHigh
            
    self.presentViewController(picker, animated: true, completion: nil)
}
概念大致上與這篇相似,不同的地方在於多了mediaType與videoQuality。mediaType是設定picker的媒體型式,這裡設定為所有形式。videoQuality是設定影片的品質,這裡設定較高的畫質。

實作影片的播放:

當使用者確認使用所拍攝的影片後會呼叫didFinishPickingMediaWithInfo:方法,如下所示。

func imagePickerController(_picker: UIImagePickerController!, didFinishPickingMediaWithInfo info: NSDictionary!) { }

將以下程式碼放置didFinishPickingMediaWithInfo:方法內。

videoURL = info[UIImagePickerControllerMediaURL] as NSURL
_picker.dismissViewControllerAnimated(true, completion: nil)
        
videoController = MPMoviePlayerController()
        
videoController.contentURL = videoURL
videoController.view.frame = CGRectMake(0, 0, 320, 460)
view.addSubview(videoController.view)
        
NSNotificationCenter.defaultCenter().addObserver(self, selector: "videoPlayBackDidFinish", name: MPMoviePlayerPlaybackDidFinishNotification, object: videoController)
        
videoController.play()
先取得影片的系統URL。將相機視窗消失。將videoController這個變數設定為MPMoviePlayerController物件。將videoController的內容URL設定為剛剛取得的URL。設定videoController的畫面大小。請求view將videoController顯示出來。MPMoviePlayerController物件有個通知功能,可以用來控制影片的播放,所以我們在這建立一個通知,當影片播完時MPMoviePlayerPlaybackDidFinishNotification就會送出,並觸發videoPlayBackDidFinish:這個方法。最後叫VideoController播放影片。

使用影片播放器通知:

當影片播放結束後,NotificationCenter會發送播放完畢的通知,並觸發videoPlayBackDidFinish:這個方法,所以我們必須建立一個videoPlayBackDidFinish:方法,處理通知產生後的動作,如下所示。

func videoPlayBackDidFinish() { }
建立一個func,讓通知產生時可以執行func內的事情。

將以下程式碼放置videoPlayBackDidFinish:方法內,如下所示。

NSNotificationCenter.defaultCenter().removeObserver(self, name: MPMoviePlayerPlaybackDidFinishNotification, object: nil)
        
videoController.stop()
videoController.view.removeFromSuperview()
videoController = nil
        
var alertView = UIAlertController(title: "Video Playback", message: "Just finished the video playback, The video is now removed", preferredStyle: .Alert)
alertView.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alertView, animated: true, completion: nil)
首先將NotificationCenter監聽通知的匹配移除。叫videoCotroller停止播放影片。將videoController的view從畫面上移除。將videoController設為空的。最後顯示UIAlertView來告訴使用者影片已經播畢。


【執行結果】

    
    


【專案範例】

Simple Camera programmatically using Swift language

【說明】

此份筆記是根據ISBN:978-986-201-900-9這本書的第23章節所紀錄的。

此份專案可以讓使用者使用內建相機拍照並在UIImageView上顯示,也可以讓使用者從相簿裡挑選照片在UIImageView上顯示。


【專案開發步驟】

建立專案:

使用Single View Application模板建立一個名為CameraApp的專案,使用iPhone裝置。

設計使用者介面:

在畫面上加入一個UIImageView以及兩個按鈕,如下圖所示。

設計User Interface
建立物件與類別的IBAction,如下圖所示。

物件與類別的IBAction
建立物件與類別的IBOutlet,如下所示。

@IBOutlet var imageView: UIImageView

實作CameraViewController:

讓ViewController繼承UIImagePickerControllerDelegate與UINavigationControllerDelegate,如下所示。

class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate { }

在takePhoto:方法裡增加下面的程式。

if (UIImagePickerController.isSourceTypeAvailable(.Camera)) {
            
    var picker: UIImagePickerController = UIImagePickerController()
    picker.delegate = self
    picker.allowsEditing = true
    picker.sourceType = .Camera
    picker.showsCameraControls = true
            
    self.presentViewController(picker, animated: true, completion: nil)

}
首先先判斷裝置是否有支援相機的功能。建立一個變數名為picker,資料型態為UIImagePickerController。將delegate設為自己。將是否允許編輯設為是。將sourceType設為相機。允許顯示相機的控制。最後想picker顯示出來。

在selectPhoto:方法裡增加下面的程式。

if (UIImagePickerController.isSourceTypeAvailable(.SavedPhotosAlbum)) {
            
    var picker: UIImagePickerController = UIImagePickerController()
    picker.delegate = self
    picker.allowsEditing = true
    picker.sourceType = .SavedPhotosAlbum
    
    self.presentViewController(picker, animated: true, completion: nil)

}
與takePhoto:方法類似,但是將sourceType設為SavePhotosAlbum。

實作UIImagePickerController的delegate:

實作以下func。

func imagePickerController(_picker: UIImagePickerController!, didFinishPickingMediaWithInfo info: NSDictionary!) { }
當使用者拍完照或是挑選完照片後會呼叫這個func。

將以下程式碼加入到didFinishPickingMediaWithInfo:方法裡。

var choseImage: UIImage = info[UIImagePickerControllerEditedImage] as UIImage
imageView.image = choseImage
        

_picker.dismissViewControllerAnimated(true, completion: nil)
建立一個變數名為choseImage,資料型態為UIImage,存放使用者所拍的照片或是所挑選的照片。將使用者的照片給imageView去顯示。最後讓視窗消失即可。


【執行結果】

    
    


【專案範例】

2014年7月11日 星期五

Core Data programmatically using Swift language(Update data)

【說明】

此份筆記是根據ISBN:978-986-201-900-9這本書的第19章節所紀錄的。

專案範例延續著這篇,新增更新資料的動作。

【專案開發步驟】

更新一個託管物件:

在TableView與NavigationController之間在新增一個Segue,從Cell到Controller,並設定Identifier為UpdateRecipe,如下圖所示。

新增Segue
在RecipeStoreTableViewController.swift檔案內加入prepareForSugue:方法,如下所示。

override func prepareForSegue(_segue: UIStoryboardSegue!, sender: AnyObject!) {
        
    if (_segue.identifier == "UpdateRecipe") {
        var selectRecipe: NSManagedObject = recipes.objectAtIndex(tableView.indexPathForSelectedRow().row) as NSManagedObject
        var destViewController: UINavigationController = _segue.destinationViewController as UINavigationController
        var recipeViewController: AddRecipeViewController = destViewController.topViewController as AddRecipeViewController
        recipeViewController.recipe = selectRecipe
    }

}
當使用者按下Cell時會觸發這個方法傳送Segue給目的地的ViewController,首先先確認所觸發的Segue是不是Identifier名為UpdateRecipe,如果是的話進迴圈裡面。建立一個NSManagedObject,名為selectRecipe,儲存使用者所選擇的資料。建立一個UINavigationController,名為destViewController,儲存Segue目的地端的ViewController。建立一個名為recipeViewController的AddRecipeViewController,利用topViewController的屬性得到Navigation的堆疊(Stack)中最上層的ViewController。透過recipeViewController將AddRecipeViewController的recipe設為selectRecipe。

修改AddRecipeViewController.swift,如下所示。

var recipe: NSManagedObject!
新增一個名為recipe的全域變數,資料型態為NSManagedObject。

override func viewDidLoad() {
    super.viewDidLoad()

    if (recipe) {
        nameTextField.text = recipe.valueForKey("name") as String
        imageTextField.text = recipe.valueForKey("image") as String
        prepTimeTextField.text = recipe.valueForKey("prepTime") as String
    }

}
recipe只有在Cell被按下時才會有值,其餘時間只是個空的變數而已,所以我們利用if去判斷當AddRecipeViewController出現的時候TextField需不需要顯示文字,若if成立將會把使用者所點選的Cell的資料顯示在TextField內。

修改AddRecipeViewController.swift內的save函式,如下所示。

@IBAction func save(sender: UIBarButtonItem) {
        
    var context: NSManagedObjectContext = self.managedObjectContext()
        
    if (recipe) {
            
        recipe.setValue(nameTextField.text, forKey: "name")
        recipe.setValue(imageTextField.text, forKey: "image")
        recipe.setValue(prepTimeTextField.text, forKey: "prepTime")
    } else {
            
        var newRecipe: NSManagedObject = NSEntityDescription.insertNewObjectForEntityForName("Recipe", inManagedObjectContext: context) as NSManagedObject
        newRecipe.setValue(nameTextField.text, forKey: "name")
        newRecipe.setValue(imageTextField.text, forKey: "image")
        newRecipe.setValue(prepTimeTextField.text, forKey: "prepTime")
    }
        
    self.dismissViewControllerAnimated(true, completion: nil)

}
差別在於我們要知道NSManagedObject是否要新建立一個,所以用if來做判斷資料是否存在。若是已有的資料,則託管物件就不需要在被建立,用舊的託管物件去設定資料的值即可將舊的覆蓋過去。若是新建立的資料則必須新建一個託管物件,使用新的託管文件將資料儲存。最後讓ViewCotroller離開即可。


【執行結果】

    



【專案範例】

2014年7月10日 星期四

Core Data programmatically using Swift language(Delete data)

【說明】

此份筆記是根據ISBN:978-986-201-900-9這本書的第19章節所紀錄的。

專案範例延續著這篇,新增刪除資料的動作。

刪除一個託管物件是很容易的,只要呼叫deleteObject:方法,並將要刪除的物件作為參數來傳遞,即可刪除資料。


【專案開發步驟】

刪除一個託管物件:

為了要讓UITableView能夠讓使用者使用刪除功能,必須實作UITableViewDataSource協定中的canEditRowAtIndexPath:與commitEditingStyle:方法如下所示。

override func tableView(tableView: UITableView!, canEditRowAtIndexPath indexPath: NSIndexPath!) -> Bool {
    return true

}
決定TableView的Row是否可以被編輯。

override func tableView(tableView: UITableView!, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath!) {
            
    var context: NSManagedObjectContext = managedObjectContext()
        
    if editingStyle == .Delete {
            
        context.deleteObject(recipes.objectAtIndex(indexPath.row) as NSManagedObject)
            
        recipes.removeObjectAtIndex(indexPath.row)
        tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
            
    } else if editingStyle == .Insert {

    }   
}
先取得託管物件本文,因為NSManagedObjectContext提供了deleteObject:方法,讓我們可以刪除特定的物件。利用本文執行deleteObject:方法,將選到的indexPath.row刪除。Core Data被刪除後也要將TableView上的資料刪除。


【執行結果】

    


【專案範例】

2014年7月9日 星期三

Core Data programmatically using Swift language

【說明】

此份筆記是根據ISBN:978-986-201-900-9這本書的第18章節所紀錄的。

Core Data與plist的差異:
plist處理小資料量時比較快速,但是會一次把文件載入記憶體內,效率上不是很優。Core Data擁有table之間的Relationship,與SQLite相似,但是不必下語法。

Core Data就是可以儲存到磁碟的物件圖,不僅僅在磁碟上儲存資料,也把我們需要的資料物件讀取到記憶體中。   - Marcus Zarra, Core Data

Core Data主要任務是負責資料更改的管理,序列化到磁碟,最小化記憶體佔用,以及查詢資料,可以將資料儲存為XML、二進制檔案或是SQLite檔案。

Core Data堆疊(Stack):
託管物件模型(Managed Object Model):描述你在程式裡使用的Schema(綱要)。
永續性儲存協調器(Persistent Store Coordinator):負責處理不同永續性物件儲存區以及將物件儲存到儲存區中。
託管物件本文(Managed Object Context):管理被建立的物件並使用Core Data回傳。


【專案開發步驟】

建立專案:

使用Empty Application模板建立專案,專案名稱設為RecipeStore,並勾選使用Core Data。

因為使用Empty Application建立專案,所以需新增Storyboard來方便我們撰寫程式,並將名稱設為Main,如下圖所示。

新增Storyboard
點選專案,將Main Interface設為Main,如下圖所示。

設定Main Interface
將didFinishLaunchingWithOptions:這個方法更新,如下所示。

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
    
    return true
}

定義託管物件模型:

在資料模型內建立一個Entity,並取名為Recipe,如下圖所示。

建立Entity
在名為Recipe的Entity內建立三個Attributes,分別為name、image、prepTime,並設三個資料型態為String,如下圖所示。

建立Attributes

設計使用者介面:

依照下圖建立好介面。

建立使用者介面
將+的Segue Identifier設為Add,如下圖所示。
設定Segue Identifier
並將Prototype Cell的Identifier設為Cell,如下圖所示。

設定Identifier為Cell
建立一個TableViewController的類別,取名為RecipeStoreTableViewController,並繼承UITableViewController,至Storyboard設定介面與自訂類別的關聯,如下圖所示。

設定頁面與自訂類別的關聯
建立一個ViewController的類別,取名為AddRecipeViewController,並繼承UIViewController,至Storyboard設定介面與自訂類別的關聯,如下圖所示。

設定頁面與自訂類別的關聯
建立AddRecipeViewController頁面上,三個TextField與兩個Bar Button的IBOutlet與IBAction,如下圖所示。

建立物件的關聯

建立託管物件:

在AppRecipeViewController.swift內建立一個func,如下所示。

func managedObjectContext() -> NSManagedObjectContext {
        
    let appDelegate: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
    let context: NSManagedObjectContext = appDelegate.managedObjectContext
        
    return context

}
透過這個func可以管理物件,經由本文就可以取得或變更託管物件。

在AppRecipeViewController.swift實作save:方法,如下所示。

@IBAction func save(sender: UIBarButtonItem) {
        
    var context: NSManagedObjectContext = self.managedObjectContext()
        
    var newRecipe: NSManagedObject = NSEntityDescription.insertNewObjectForEntityForName("Recipe", inManagedObjectContext: context) as NSManagedObject
    newRecipe.setValue(nameTextField.text, forKey: "name")
    newRecipe.setValue(imageTextField.text, forKey: "image")
    newRecipe.setValue(prepTimeTextField.text, forKey: "prepTime")
        
    self.dismissViewControllerAnimated(true, completion: nil)

}
先取得託管物件本文,然後針對新的食譜建立一個NSManagedObject,並將託管物件跟Entity關聯在一起。NSManagedObject就像是NSDictionary一樣,一旦建立託管物件(newRecipe)後,即可將Entity的屬性當作Key一樣來設定value。最後dismissViewControllerAnimated用來解除目前的ViewController。

在AppRecipeViewController.swift實作cancel:方法,如下所示。

@IBAction func cancel(sender: UIBarButtonItem) {
        
    self.dismissViewControllerAnimated(true, completion: nil)

}
解除目前的ViewController。

取得託管物件:

在RecipeStoreTableViewController.swift內建立一個名為recipes的可變陣列,如下所示。

var recipes: NSMutableArray = []
資料型態為NSMutableArray的陣列變數,名為recipes。

在RecipeStoreTableViewController.swift內建立一個func,取得託管物件文本,與AppRecipeViewController.swift內的func相同。

在RecipeStoreTableViewController.swift內增加viewDidAppear方法,如下所示。

override func viewDidAppear(animated: Bool)  {
    super.viewDidAppear(true)

    var managedObjectContext = self.managedObjectContext()
    let fetchRequest = NSFetchRequest(entityName: "Recipe")
    recipes = NSMutableArray(array: managedObjectContext.executeFetchRequest(fetchRequest, error: nil))
        
    self.tableView.reloadData()

}
為了要取得資料,必須取得本文(Context)。建立一個提取需求(fetch request),並指定要去提取的Entity。利用Recipe entity建立一個NSFetchRequest的實體,並用executeFetchRequest:這個方法取得資料庫的所有食譜。最後讓tableView重新載入。
viewDidAppear方法是在View確實被顯示出來時才會呼叫的,利用這個方法讓新食譜被建立後重新載入資料,但這不是好的方法,因為若使用者不想新建食譜時按下取消也會被呼叫。

在TableView中填入食譜:

在RecipeStoreTableViewController.swift內實作以下方法。

override func numberOfSectionsInTableView(tableView: UITableView!) -> Int {
    return 1

}
回傳有幾個Section在TableView裏面。

override func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
    return recipes.count

}
告訴ViewController要顯示多少個Cell。

override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell? {
        
    var cellIdentifier: String = "Cell"
    var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as UITableViewCell
        
    if cell == nil {
        cell = UITableViewCell(style: .Default, reuseIdentifier: cellIdentifier)
    }

    var recipe: NSManagedObject = recipes.objectAtIndex(indexPath.row) as NSManagedObject
    
    cell.textLabel.text = recipe.valueForKey("name") as String
    var imageString: String = recipe.valueForKey("image") as String
    var prepTimeString: String = recipe.valueForKey("prepTime") as String
    cell.detailTextLabel.text = "\(imageString) - \(prepTimeString)"
        
    return cell

}
在TableView內顯示資料,可參考這篇。NSManagedObject與NSDictionary非常像,使用valueForKey方法就可以取得屬性值。將食譜名稱作為標題,圖檔名與準備時間以子標題方式呈現。


【執行結果】

    

    


【專案範例】